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
- You register a webhook endpoint URL
- You specify which events to subscribe to
- When an event occurs, Coherence sends an HTTP POST to your URL
- 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
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The HTTPS URL to receive webhook payloads |
events | array | Yes | List of event types to subscribe to |
description | string | No | Human-readable description |
modules | array | No | Filter 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.
| Event | Description |
|---|---|
record.created | A new record was created |
record.updated | An existing record was modified |
record.deleted | A record was deleted |
Email Events
Triggered for email activity.
| Event | Description |
|---|---|
email.received | A new email was received and linked to a record |
email.sent | An email was sent from Coherence |
Task Events
Triggered for task status changes.
| Event | Description |
|---|---|
task.completed | A task was marked as complete |
task.overdue | A task passed its due date |
Automation Events
Triggered when automations run.
| Event | Description |
|---|---|
automation.triggered | An automation started executing |
automation.completed | An 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
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID for idempotency |
event | string | The event type that triggered the webhook |
timestamp | string | ISO 8601 timestamp of when the event occurred |
webhook_id | string | The ID of the webhook subscription |
data | object | Event-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
- Extract the signature from the
X-Coherence-Signatureheader - Remove the
sha256=prefix - Compute HMAC-SHA256 of the raw request body using your webhook secret
- Compare your computed signature with the received signature
- 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}), 200Always 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
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 24 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:
- Go to webhook.site
- Copy your unique URL
- Create a webhook with that URL
- Trigger events and see payloads in real-time
Local Development with ngrok
Test webhooks against your local development server:
- Install ngrok
- Start your local server (e.g.,
http://localhost:3000) - Run ngrok:
ngrok http 3000- 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
| Issue | Solution |
|---|---|
| Not receiving webhooks | Check webhook is active and URL is accessible |
| Signature verification fails | Ensure you're using the raw request body, not parsed JSON |
| Duplicate events | Implement idempotency using event ID |
| Timeouts | Respond within 5 seconds, process async |
| HTTPS errors | Ensure 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