Skip to content

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

  1. Create a new scenario in Make.com

  2. 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)
  3. Create webhook subscription in the platform:

    bash
    curl -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"]
      }'
  4. Save the signing secret from the response (you'll need it for verification)

  1. Add signature verification (optional but recommended):

    • Add a "Tools > Set variable" module
    • Set signature to {{1.headers.x-webhook-signature}}
    • Add a "HTTP > Verify webhook signature" module
    • Algorithm: sha256
    • Secret: Your signing secret
    • Body: {{1.body}}
  2. 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.

Example: Send Slack notification on appointment creation

Modules:

  1. Webhook — Receives event from the Core API
  2. Router — Filters by event type
  3. Slack > Create a message — Sends notification

Filter (between Webhook and Slack):

  • Condition: {{parseJSON(1.body).event}} equals appointment.created

Slack message:

v-pre
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:

v-pre
GET https://api.example.com/v1/appointments/{{parseJSON(1.body).data.appointment_id}}
Authorization: Bearer YOUR_API_KEY

Zapier

Zapier is another popular automation platform with webhook support.

Setup

  1. Create a new Zap

  2. Choose trigger:

    • Search for "Webhooks by Zapier"
    • Select "Catch Hook"
    • Copy the webhook URL
  3. Create webhook subscription in the platform:

    bash
    curl -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": ["*"]
      }'
  4. Test the trigger:

    • Use the Core API's test endpoint to send a test webhook
    • Zapier will capture the payload structure
  5. Add actions:

    • Filter by event type: event equals appointment.created
    • Access data: data__appointment_id, data__patient_id, etc.

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

  1. Create a new workflow

  2. 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)
  3. Create webhook subscription in the platform:

    bash
    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-n8n.com/webhook/core-webhook",
        "description": "n8n workflow",
        "events": ["appointment.created", "appointment.cancelled"]
      }'
  4. 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];
  1. Add workflow logic:
    • Access event: {{$json.body.event}}
    • Access data: {{$json.body.data.appointment_id}}

Example: Update database on form submission

Workflow:

  1. Webhook — Receives Core API event
  2. Function — Verifies signature
  3. Switch — Routes by event type
    • Case 1: form.submitted
    • Case 2: form.signed
  4. 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
  5. 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}}

Custom Node.js Application

Express.js Example

javascript
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

bash
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

python
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:

bash
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:

bash
curl https://api.example.com/v1/webhook-subscriptions/{uid}/events \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"

3. Monitor Failed Deliveries

Filter for failures:

bash
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:

javascript
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:

javascript
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

javascript
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:

javascript
// 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