Webhooks

Receive real-time notifications when events occur in your workspace

Webhooks allow your application to receive real-time HTTP callbacks when events occur in Coherence. Instead of polling the API, webhooks push data to your server as events happen.

Overview

When to Use Webhooks

Use webhooks when you need to:

  • Sync data to external systems in real-time
  • Trigger workflows when records change
  • Send notifications based on activity
  • Keep external databases in sync
  • Build integrations that react to events

How Webhooks Work

  1. You register a webhook endpoint URL
  2. You specify which events to subscribe to
  3. When an event occurs, Coherence sends an HTTP POST to your URL
  4. Your server processes the payload and responds with 200 OK

Creating Webhooks

Create a Webhook

curl -X POST "https://api.getcoherence.io/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/coherence",
    "events": ["record.created", "record.updated"],
    "description": "Sync contacts to CRM"
  }'

Response:

{
  "data": {
    "id": "wh_abc123xyz",
    "url": "https://yourapp.com/webhooks/coherence",
    "events": ["record.created", "record.updated"],
    "description": "Sync contacts to CRM",
    "secret": "whsec_7f8a9b2c3d4e5f6g7h8i9j0k",
    "active": true,
    "created_at": "2024-01-15T10:30:00Z"
  }
}

The webhook secret is shown only once. Store it securely - you'll need it to verify webhook signatures.

Request Parameters

ParameterTypeRequiredDescription
urlstringYesThe HTTPS URL to receive webhook payloads
eventsarrayYesList of event types to subscribe to
descriptionstringNoHuman-readable description
modulesarrayNoFilter events to specific modules

Managing Webhooks

List Webhooks

curl -X GET "https://api.getcoherence.io/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response:

{
  "data": [
    {
      "id": "wh_abc123xyz",
      "url": "https://yourapp.com/webhooks/coherence",
      "events": ["record.created", "record.updated"],
      "description": "Sync contacts to CRM",
      "active": true,
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "meta": {
    "total": 1
  }
}

Get a Webhook

curl -X GET "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz" \
  -H "Authorization: Bearer YOUR_API_KEY"

Update a Webhook

curl -X PATCH "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["record.created", "record.updated", "record.deleted"],
    "active": true
  }'

Delete a Webhook

curl -X DELETE "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz" \
  -H "Authorization: Bearer YOUR_API_KEY"

Rotate Webhook Secret

Generate a new signing secret:

curl -X POST "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz/rotate-secret" \
  -H "Authorization: Bearer YOUR_API_KEY"

Event Types

Record Events

Triggered when records are created, updated, or deleted in any module.

EventDescription
record.createdA new record was created
record.updatedAn existing record was modified
record.deletedA record was deleted

Email Events

Triggered for email activity.

EventDescription
email.receivedA new email was received and linked to a record
email.sentAn email was sent from Coherence

Task Events

Triggered for task status changes.

EventDescription
task.completedA task was marked as complete
task.overdueA task passed its due date

Automation Events

Triggered when automations run.

EventDescription
automation.triggeredAn automation started executing
automation.completedAn automation finished executing

Payload Format

All webhook payloads follow a standard envelope format.

Standard Envelope

{
  "id": "evt_xyz789abc",
  "event": "record.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "webhook_id": "wh_abc123xyz",
  "data": {
    "module": "contacts",
    "record_id": "rec_def456",
    "record": {
      "id": "rec_def456",
      "name": "John Smith",
      "email": "[email protected]",
      "created_at": "2024-01-15T10:30:00Z"
    }
  }
}

Envelope Fields

FieldTypeDescription
idstringUnique event ID for idempotency
eventstringThe event type that triggered the webhook
timestampstringISO 8601 timestamp of when the event occurred
webhook_idstringThe ID of the webhook subscription
dataobjectEvent-specific payload data

Record Event Payloads

record.created / record.updated:

{
  "id": "evt_xyz789abc",
  "event": "record.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "webhook_id": "wh_abc123xyz",
  "data": {
    "module": "contacts",
    "record_id": "rec_def456",
    "record": {
      "id": "rec_def456",
      "name": "John Smith",
      "email": "[email protected]",
      "company": "Acme Corp",
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T10:30:00Z"
    },
    "changed_fields": ["name", "email"],
    "changed_by": {
      "id": "usr_abc123",
      "email": "[email protected]"
    }
  }
}

record.deleted:

{
  "id": "evt_xyz789abc",
  "event": "record.deleted",
  "timestamp": "2024-01-15T10:30:00Z",
  "webhook_id": "wh_abc123xyz",
  "data": {
    "module": "contacts",
    "record_id": "rec_def456",
    "deleted_by": {
      "id": "usr_abc123",
      "email": "[email protected]"
    }
  }
}

Email Event Payloads

{
  "id": "evt_xyz789abc",
  "event": "email.received",
  "timestamp": "2024-01-15T10:30:00Z",
  "webhook_id": "wh_abc123xyz",
  "data": {
    "email_id": "eml_abc123",
    "subject": "Re: Partnership Discussion",
    "from": "[email protected]",
    "to": ["[email protected]"],
    "linked_records": [
      {
        "module": "contacts",
        "record_id": "rec_def456"
      }
    ]
  }
}

Task Event Payloads

{
  "id": "evt_xyz789abc",
  "event": "task.completed",
  "timestamp": "2024-01-15T10:30:00Z",
  "webhook_id": "wh_abc123xyz",
  "data": {
    "task_id": "tsk_abc123",
    "title": "Follow up with client",
    "completed_at": "2024-01-15T10:30:00Z",
    "completed_by": {
      "id": "usr_abc123",
      "email": "[email protected]"
    },
    "linked_record": {
      "module": "deals",
      "record_id": "rec_xyz789"
    }
  }
}

Automation Event Payloads

{
  "id": "evt_xyz789abc",
  "event": "automation.completed",
  "timestamp": "2024-01-15T10:30:00Z",
  "webhook_id": "wh_abc123xyz",
  "data": {
    "automation_id": "auto_abc123",
    "automation_name": "Welcome Email Sequence",
    "trigger_record": {
      "module": "contacts",
      "record_id": "rec_def456"
    },
    "status": "success",
    "actions_executed": 3,
    "duration_ms": 1250
  }
}

Webhook Security

Signature Verification

Every webhook request includes a signature in the X-Coherence-Signature header. Verify this signature to ensure the request came from Coherence and wasn't tampered with.

The signature is an HMAC-SHA256 hash of the raw request body using your webhook secret.

Signature Header Format

X-Coherence-Signature: sha256=a1b2c3d4e5f6...

Verification Process

  1. Extract the signature from the X-Coherence-Signature header
  2. Remove the sha256= prefix
  3. Compute HMAC-SHA256 of the raw request body using your webhook secret
  4. Compare your computed signature with the received signature
  5. Reject the request if signatures don't match

Node.js Verification Example

import crypto from 'crypto';
import express from 'express';
 
const app = express();
 
// Use raw body for signature verification
app.use('/webhooks', express.raw({ type: 'application/json' }));
 
const WEBHOOK_SECRET = process.env.COHERENCE_WEBHOOK_SECRET;
 
function verifySignature(payload: Buffer, signature: string): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
 
  const receivedSignature = signature.replace('sha256=', '');
 
  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(receivedSignature)
  );
}
 
app.post('/webhooks/coherence', (req, res) => {
  const signature = req.headers['x-coherence-signature'] as string;
 
  if (!signature || !verifySignature(req.body, signature)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
 
  const event = JSON.parse(req.body.toString());
 
  // Process the event
  console.log('Received event:', event.event);
 
  // Respond quickly
  res.status(200).json({ received: true });
});

Python Verification Example

import hmac
import hashlib
from flask import Flask, request, jsonify
 
app = Flask(__name__)
 
WEBHOOK_SECRET = os.environ.get('COHERENCE_WEBHOOK_SECRET')
 
def verify_signature(payload: bytes, signature: str) -> bool:
    expected_signature = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
 
    received_signature = signature.replace('sha256=', '')
 
    return hmac.compare_digest(expected_signature, received_signature)
 
@app.route('/webhooks/coherence', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Coherence-Signature')
 
    if not signature or not verify_signature(request.data, signature):
        return jsonify({'error': 'Invalid signature'}), 401
 
    event = request.get_json()
 
    # Process the event
    print(f"Received event: {event['event']}")
 
    # Respond quickly
    return jsonify({'received': True}), 200

Always use constant-time comparison functions like crypto.timingSafeEqual or hmac.compare_digest to prevent timing attacks.

Retry Logic

If your endpoint fails to respond with a 2xx status code, Coherence will retry the webhook delivery.

Retry Schedule

AttemptDelay
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry24 hours

After 5 failed attempts, the webhook is marked as failed and no further retries occur.

Retry Headers

Retry requests include additional headers:

X-Coherence-Retry-Count: 2
X-Coherence-Original-Timestamp: 2024-01-15T10:30:00Z

Automatic Disabling

If a webhook fails consistently (more than 100 failures in 24 hours), it will be automatically disabled. You'll receive an email notification when this happens.

To re-enable:

curl -X PATCH "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active": true}'

Best Practices

Respond Quickly

Your webhook endpoint should respond within 5 seconds. If processing takes longer, acknowledge the webhook immediately and process asynchronously.

app.post('/webhooks/coherence', async (req, res) => {
  // Acknowledge immediately
  res.status(200).json({ received: true });
 
  // Process asynchronously
  processWebhookAsync(req.body);
});
 
async function processWebhookAsync(event) {
  // Long-running processing here
  await syncToCRM(event.data.record);
  await updateAnalytics(event);
}

Handle Duplicates

Webhooks may be delivered more than once. Use the event id to implement idempotency.

const processedEvents = new Set();
 
async function handleWebhook(event) {
  // Check if already processed
  if (processedEvents.has(event.id)) {
    console.log('Duplicate event, skipping:', event.id);
    return;
  }
 
  // Process the event
  await processEvent(event);
 
  // Mark as processed
  processedEvents.add(event.id);
}

For production, store processed event IDs in a database with a TTL of at least 24 hours to handle retries.

Use a Queue

For high-volume webhooks, queue events for background processing:

import { Queue } from 'bullmq';
 
const webhookQueue = new Queue('webhooks');
 
app.post('/webhooks/coherence', async (req, res) => {
  await webhookQueue.add('process', req.body);
  res.status(200).json({ received: true });
});

Filter Events

Subscribe only to the events you need to reduce unnecessary traffic:

{
  "url": "https://yourapp.com/webhooks",
  "events": ["record.created"],
  "modules": ["contacts", "deals"]
}

Monitor Webhook Health

Check delivery status regularly:

curl -X GET "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz/deliveries" \
  -H "Authorization: Bearer YOUR_API_KEY"

Response:

{
  "data": [
    {
      "id": "del_abc123",
      "event_id": "evt_xyz789",
      "status": "success",
      "response_code": 200,
      "duration_ms": 145,
      "delivered_at": "2024-01-15T10:30:00Z"
    }
  ]
}

Testing Webhooks

Using webhook.site

For quick testing without setting up a server:

  1. Go to webhook.site
  2. Copy your unique URL
  3. Create a webhook with that URL
  4. Trigger events and see payloads in real-time

Local Development with ngrok

Test webhooks against your local development server:

  1. Install ngrok
  2. Start your local server (e.g., http://localhost:3000)
  3. Run ngrok:
ngrok http 3000
  1. Use the ngrok URL when creating your webhook:
curl -X POST "https://api.getcoherence.io/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://abc123.ngrok.io/webhooks/coherence",
    "events": ["record.created"]
  }'

Test Endpoint

Send a test event to verify your endpoint is working:

curl -X POST "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz/test" \
  -H "Authorization: Bearer YOUR_API_KEY"

This sends a test event with sample data to your webhook URL.

Replay Failed Deliveries

Retry a specific failed delivery:

curl -X POST "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz/deliveries/del_abc123/retry" \
  -H "Authorization: Bearer YOUR_API_KEY"

Troubleshooting

Common Issues

IssueSolution
Not receiving webhooksCheck webhook is active and URL is accessible
Signature verification failsEnsure you're using the raw request body, not parsed JSON
Duplicate eventsImplement idempotency using event ID
TimeoutsRespond within 5 seconds, process async
HTTPS errorsEnsure valid SSL certificate on your endpoint

Webhook Logs

View recent delivery attempts:

curl -X GET "https://api.getcoherence.io/v1/webhooks/wh_abc123xyz/deliveries?status=failed" \
  -H "Authorization: Bearer YOUR_API_KEY"

Related: API Overview | Authentication