Webhook Integration Examples
This guide shows how to integrate platform webhooks with popular automation platforms and custom applications.
Make.com (formerly Integromat)
Make.com is a visual automation platform that can receive webhooks and trigger actions.
Setup
Create a new scenario in Make.com
Add a Webhook trigger:
- Click "+" to add a module
- Search for "Webhooks"
- Select "Custom webhook"
- Click "Add" to create a new webhook
- Copy the webhook URL (e.g.,
https://hook.eu1.make.com/abc123xyz)
Create webhook subscription in the platform:
bashcurl -X POST https://api.example.com/v1/webhook-subscriptions \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hook.eu1.make.com/abc123xyz", "description": "Make.com automation", "events": ["appointment.created", "form.signed"] }'Save the signing secret from the response (you'll need it for verification)
Add signature verification (optional but recommended):
- Add a "Tools > Set variable" module
- Set
signatureto{{1.headers.x-webhook-signature}} - Add a "HTTP > Verify webhook signature" module
- Algorithm:
sha256 - Secret: Your signing secret
- Body:
{{1.body}}
Add your automation logic:
- Parse the webhook payload:
{{parseJSON(1.body)}} - Access event data:
{{parseJSON(1.body).data.appointment_id}} - Add modules for Slack, email, database updates, etc.
- Parse the webhook payload:
Example: Send Slack notification on appointment creation
Modules:
- Webhook — Receives event from the Core API
- Router — Filters by event type
- Slack > Create a message — Sends notification
Filter (between Webhook and Slack):
- Condition:
{{parseJSON(1.body).event}}equalsappointment.created
Slack message:
New appointment created!
ID: {{parseJSON(1.body).data.appointment_id}}
Specialist: {{parseJSON(1.body).data.specialist_id}}
Time: {{parseJSON(1.body).data.scheduled_at}}To get full details, add an HTTP module to call Core API:
GET https://api.example.com/v1/appointments/{{parseJSON(1.body).data.appointment_id}}
Authorization: Bearer YOUR_API_KEYZapier
Zapier is another popular automation platform with webhook support.
Setup
Create a new Zap
Choose trigger:
- Search for "Webhooks by Zapier"
- Select "Catch Hook"
- Copy the webhook URL
Create webhook subscription in the platform:
bashcurl -X POST https://api.example.com/v1/webhook-subscriptions \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.zapier.com/hooks/catch/123456/xyz", "description": "Zapier automation", "events": ["*"] }'Test the trigger:
- Use the Core API's test endpoint to send a test webhook
- Zapier will capture the payload structure
Add actions:
- Filter by event type:
eventequalsappointment.created - Access data:
data__appointment_id,data__patient_id, etc.
- Filter by event type:
Example: Create Google Calendar event on appointment
Trigger: Webhooks by Zapier — Catch Hook
Filter: Only continue if event equals appointment.created
Action 1: HTTP — GET Request
- URL:
https://api.example.com/v1/appointments/{{data__appointment_id}} - Headers:
Authorization: Bearer YOUR_API_KEY
Action 2: Google Calendar — Create Event
- Title:
{{data.title}}(from API response) - Start time:
{{data.scheduled_at}} - Description:
Patient ID: {{data.patient_id}}
n8n
n8n is an open-source workflow automation tool with powerful webhook capabilities.
Setup
Create a new workflow
Add a Webhook node:
- Set "HTTP Method" to
POST - Set "Path" to
/core-webhook(or any path you want) - Copy the webhook URL (e.g.,
https://your-n8n.com/webhook/core-webhook)
- Set "HTTP Method" to
Create webhook subscription in the platform:
bashcurl -X POST https://api.example.com/v1/webhook-subscriptions \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-n8n.com/webhook/core-webhook", "description": "n8n workflow", "events": ["appointment.created", "appointment.cancelled"] }'Add signature verification:
- Add a "Function" node after the Webhook
- Code:javascript
const crypto = require('crypto'); const signature = $node["Webhook"].json.headers['x-webhook-signature']; const secret = 'whsec_YOUR_SIGNING_SECRET'; const body = JSON.stringify($node["Webhook"].json.body); const hmac = crypto.createHmac('sha256', secret); hmac.update(body); const expected = 'sha256=' + hmac.digest('hex'); if (expected !== signature) { throw new Error('Invalid signature'); } return [$node["Webhook"].json];
- Add workflow logic:
- Access event:
{{$json.body.event}} - Access data:
{{$json.body.data.appointment_id}}
- Access event:
Example: Update database on form submission
Workflow:
- Webhook — Receives Core API event
- Function — Verifies signature
- Switch — Routes by event type
- Case 1:
form.submitted - Case 2:
form.signed
- Case 1:
- HTTP Request — Fetch form details from Core API
- URL:
https://api.example.com/v1/forms/{{$json.body.data.form_id}} - Headers:
Authorization: Bearer YOUR_API_KEY
- URL:
- PostgreSQL — Insert into your database
- Query:
INSERT INTO form_submissions (form_id, status, data) VALUES ($1, $2, $3) - Parameters:
{{$json.body.data.form_id}},completed,{{$json}}
- Query:
Custom Node.js Application
Express.js Example
const express = require('express');
const crypto = require('crypto');
const app = express();
// IMPORTANT: Use raw body for signature verification
app.use('/webhook', express.raw({ type: 'application/json' }));
const WEBHOOK_SECRET = process.env.CORE_API_WEBHOOK_SECRET; // whsec_...
function verifySignature(body, signature) {
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
hmac.update(body);
const expected = 'sha256=' + hmac.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
app.post('/webhook', async (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!signature) {
return res.status(401).send('Missing signature');
}
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Invalid signature');
}
// Parse the event
const event = JSON.parse(req.body);
console.log('Received event:', event.event);
// Handle different event types
switch (event.event) {
case 'appointment.created':
await handleAppointmentCreated(event.data);
break;
case 'form.signed':
await handleFormSigned(event.data);
break;
default:
console.log('Unhandled event:', event.event);
}
// Return 200 immediately (process async)
res.status(200).send('OK');
});
async function handleAppointmentCreated(data) {
// Fetch full appointment details from Core API
const response = await fetch(
`https://api.example.com/v1/appointments/${data.appointment_id}`,
{
headers: {
'Authorization': `Bearer ${process.env.CORE_API_KEY}`
}
}
);
const appointment = await response.json();
// Your business logic here
console.log('New appointment:', appointment);
// Send email, update CRM, create calendar event, etc.
}
async function handleFormSigned(data) {
// Fetch form details
const response = await fetch(
`https://api.example.com/v1/forms/${data.form_id}`,
{
headers: {
'Authorization': `Bearer ${process.env.CORE_API_KEY}`
}
}
);
const form = await response.json();
// Your business logic here
console.log('Form signed:', form);
// Archive to external system, trigger workflow, etc.
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});Create Subscription
curl -X POST https://api.example.com/v1/webhook-subscriptions \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhook",
"description": "Custom Node.js app",
"events": ["*"]
}'Custom Python Application
Flask Example
from flask import Flask, request, abort
import hmac
import hashlib
import json
import os
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('CORE_API_WEBHOOK_SECRET') # whsec_...
def verify_signature(body, signature):
mac = hmac.new(WEBHOOK_SECRET.encode(), body, hashlib.sha256)
expected = 'sha256=' + mac.hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
if not signature:
abort(401, 'Missing signature')
# Get raw body bytes
body = request.get_data()
if not verify_signature(body, signature):
abort(401, 'Invalid signature')
# Parse event
event = json.loads(body)
print(f"Received event: {event['event']}")
# Handle different event types
event_type = event['event']
if event_type == 'appointment.created':
handle_appointment_created(event['data'])
elif event_type == 'form.signed':
handle_form_signed(event['data'])
else:
print(f"Unhandled event: {event_type}")
return 'OK', 200
def handle_appointment_created(data):
# Fetch full appointment from Core API
import requests
response = requests.get(
f"https://api.example.com/v1/appointments/{data['appointment_id']}",
headers={'Authorization': f"Bearer {os.environ.get('CORE_API_KEY')}"}
)
appointment = response.json()
print(f"New appointment: {appointment}")
# Your business logic here
def handle_form_signed(data):
# Your business logic here
print(f"Form signed: {data['form_id']}")
if __name__ == '__main__':
app.run(port=3000)Testing Your Integration
1. Use the Test Endpoint
After creating a subscription, send a test webhook:
curl -X POST https://api.example.com/v1/webhook-subscriptions/{uid}/test \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"This sends a webhook.test event to verify connectivity.
2. Check Delivery Logs
View delivery attempts:
curl https://api.example.com/v1/webhook-subscriptions/{uid}/events \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"3. Monitor Failed Deliveries
Filter for failures:
curl https://api.example.com/v1/webhook-subscriptions/{uid}/events?status=failed \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"Best Practices
1. Return 2xx Quickly
Always return a 2xx response immediately after signature verification. Process the webhook asynchronously:
app.post('/webhook', (req, res) => {
// Verify signature
if (!verifySignature(req.body, req.headers['x-webhook-signature'])) {
return res.status(401).send('Invalid signature');
}
// Queue for async processing
queue.add('webhook', JSON.parse(req.body));
// Return immediately
res.status(200).send('OK');
});2. Implement Idempotency
Use the event id to prevent duplicate processing:
const processedEvents = new Set();
async function handleWebhook(event) {
if (processedEvents.has(event.id)) {
console.log('Already processed:', event.id);
return;
}
// Process event
await processEvent(event);
// Mark as processed
processedEvents.add(event.id);
}For production, use a database or cache instead of an in-memory Set.
3. Handle All Event Types Gracefully
switch (event.event) {
case 'appointment.created':
await handleAppointmentCreated(event.data);
break;
// ... other cases
default:
console.log('Unknown event type:', event.event);
// Don't throw an error — just log and continue
}4. Fetch Full Data When Needed
Webhooks contain only IDs. Use the API to get full details:
// Webhook: { appointment_id: 1234 }
// API call to get: { id: 1234, patient: { name: "..." }, ... }
const appointment = await fetch(
`https://api.example.com/v1/appointments/${data.appointment_id}`,
{ headers: { 'Authorization': `Bearer ${API_KEY}` } }
).then(r => r.json());5. Secure Your Endpoint
- Always verify signatures
- Use HTTPS only
- Keep your signing secret secure (environment variables, not hardcoded)
- Rate limit webhook endpoints to prevent abuse
Troubleshooting
Webhook not receiving events:
- Check subscription is
is_active: true - Verify URL is correct and accessible
- Check firewall rules allow the platform's IP ranges
- Review delivery logs for errors
Signature verification fails:
- Use the raw request body (not parsed/re-serialized JSON)
- Verify you're using the correct signing secret
- Check for trailing newlines or encoding issues
High latency/timeouts:
- Return 2xx within 10 seconds
- Move processing to async queue
- Check your endpoint's performance