This is more of a technical article but in short, Commslayer webhooks deliver real-time event notifications to your server over HTTPS.
Example use cases
-
Sync tickets to your CRM: Listen for
conversation_createdandconversation_updatedevents to automatically create and update records in Salesforce, HubSpot, or any CRM, keeping your sales team in the loop without manual data entry. -
Trigger order actions from customer replies: Use
message_createdevents to detect when a customer confirms a cancellation or address change, then call your fulfillment API to process it instantly. - Feed support data into your analytics pipeline: Stream every event to a data warehouse like BigQuery or Snowflake to build custom dashboards, track resolution times, or run sentiment analysis across all customer interactions.
Every delivery is signed with HMAC-SHA256 using a shared secret, includes a timestamp for replay protection, and carries a stable delivery_id for deduplication. Failed deliveries are retried up to 20 times with exponential backoff, and a circuit breaker protects both sides during extended outages. You can test any event type on demand, inspect delivery logs, and redeliver missed events through the API.
Create a webhook
Managers and administrators can add webhooks in the app. Webhooks are included in every plan on our pricing page.
- Go to Settings → Webhooks and click Add new webhook.
- Enter the Webhook URL of your server.
- Tick the Events you want: Conversation created, Conversation status changed, Conversation updated, Message created, Message updated, Live chat widget opened by the user, Contact created, Contact updated.
- Click Create webhook.
- Copy the Signing secret. It is shown only once.
Open a webhook with View details to see its status, success rate and Recent deliveries. From there you can click Test to send a sample Message created delivery to your URL, or use Reset circuit breaker after an outage on your side. Administrators can also use Rotate secret.
Message created fires for messages from customers, replies from your team and the AI agent, and private notes. To act only on customer messages, check that message_type is incoming and private is false.
1. Signature header name
X-Commslayer-Signature, value format sha256=<signature>. Every delivery also carries X-Commslayer-Timestamp, X-Commslayer-Delivery-ID, and X-Commslayer-Event. Read header names case-insensitively.
2. HMAC/hash algorithm
HMAC-SHA256. The key is your webhook's secret - a 64-character hex string shown once when you create the webhook, in the app or in the POST /api/integration/v1/webhooks response. After that you only get a new one, with Rotate secret in the app (administrators) or POST .../webhooks/:id/rotate_secret.
3. Exact bytes that are signed
"{timestamp}.{raw_request_body}" - the Unix-seconds timestamp string, a literal dot, then the request body exactly as received. Always verify against the raw bytes; never re-serialize the parsed JSON.
4. Signature encoding
Lowercase hex, prefixed with sha256=.
5. Timestamp header and replay window
X-Commslayer-Timestamp, Unix epoch seconds. We don't enforce a window server-side - reject anything more than 5 minutes from your clock. Every retry and redelivery is re-signed with a fresh timestamp, so the tight window never conflicts with our retry schedule. Replay protection = valid signature + fresh timestamp + dedupe on delivery_id from the payload (see 7); the payload copy is covered by the signature, so it can't be forged or swapped.
6. Retry schedule and timeout
10-second timeout per attempt; success is any 2xx - return it before doing heavy processing. On failure: up to 20 attempts with exponential backoff (5s, 10s, 20s, doubling, capped at 1 hour), spanning roughly 10.5 hours. Sustained failures trip a circuit breaker that pauses deliveries for up to 1 hour; anything dropped during a pause appears in GET .../webhooks/:id/delivery_logs with status skipped and can be resent via POST .../webhooks/:id/delivery_logs/:log_id/redeliver. Roughly a day of continuous failure disables the webhook and emails your account admins; recover with POST .../reset_circuit_breaker or by re-enabling the webhook, then redeliver what you missed.
7. Unique delivery ID
In two places with the same value: a top-level delivery_id field inside the payload (covered by the signature - use this as your dedupe key) and the X-Commslayer-Delivery-ID header (convenience copy). It's stable across all retries and manual redeliveries of the same delivery. The event type is in the payload's event field and the X-Commslayer-Event header. Duplicates and out-of-order arrival are both possible - dedupe on delivery_id and don't assume ordering.
8. Example payload and verification code
POST /api/integration/v1/webhooks/:id/test sends a realistic sample of any event type through the production delivery path - identical headers, signing, and payload structure to real events, delivered once with no retries. A message_created payload looks like:
{
"event": "message_created",
"id": 999001,
"content": "Hello, I need help with my recent order #12345...",
"content_type": "text",
"message_type": "incoming",
"private": false,
"created_at": "2026-08-10T13:47:57Z",
"source_id": null,
"conversation": { "id": 1001, "status": "open", "...": "..." },
"inbox": { "id": 1, "name": "Support Inbox" },
"sender": { "...": "..." },
"account": { "id": 1, "name": "..." },
"attachments": [],
"delivery_id": "7424357d-9166-4f47-98c7-9b39af82f0a1"
}
Verification (Node):
const crypto = require('crypto');
function verifyWebhook(rawBody, headers, secret) {
const ts = headers['x-commslayer-timestamp'];
const sig = headers['x-commslayer-signature']; // "sha256=<hex>"
const expected = 'sha256=' +
crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
if (expected.length !== sig.length) return false;
const authentic = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
return authentic && fresh;
}
// After verification, dedupe on JSON.parse(rawBody).delivery_id
Two integration notes: parse payloads permissively (we may add fields over time - don't reject unknown properties), and monitor your webhook's health via GET /api/integration/v1/webhooks/:id, which exposes healthy, active, and circuit-breaker state.