Skip to content

Outbound webhooks

Outbound webhook subscriptions send an HTTP POST with a JSON body to your endpoint when a selected event occurs. Open Settings → Connections → Connect WhatsApp, tools & apps (/settings?area=connections&goal=connect), then expand Advanced to find Webhook Subscriptions. Choose Add Subscription or edit an existing subscription. Choose events from Event Types; select an event and expand View request details to inspect its fields and example.

Set a Signing Secret (optional) to enable request signatures. Keep the same secret on your receiver. Without a secret, Kasbly omits the signature header. The signing secret is separate from optional Bearer or Basic authentication.

Header Value
Content-Type application/json
X-Kasbly-Event Selected event name, for example conversation.started; test for a test send.
X-Kasbly-Signature Only when a signing secret is configured: HMAC-SHA256 of the exact raw JSON request body, encoded as 64 lowercase hexadecimal characters. No sha256= prefix.
X-Retry-Attempt Omitted on the initial delivery and test sends. On retries, the total attempt number: 2 on the first retry, 3 on the second. Manual retries use the selected delivery log’s attempt number plus 1.
Authorization Only when authentication is configured: Bearer <token> or Basic <base64(username:password)>.

HTTP header names are case-insensitive; your server framework may expose them in lowercase (for example x-kasbly-signature).

Use the original request bytes, captured before JSON middleware changes the body. Parsing and serializing JSON again can change whitespace or key order and invalidate the signature. Hash the entire body, including any envelope.

This Node.js verifier rejects missing or malformed signatures, checks the length before comparing, and uses a timing-safe comparison:

import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody is the original request Buffer, before JSON parsing.
export function verifyWebhookSignature(rawBody, signature, secret) {
if (
typeof signature !== 'string' ||
signature.length !== 64 ||
!/^[0-9a-f]{64}$/.test(signature)
) {
return false;
}
const expected = createHmac('sha256', secret).update(rawBody).digest();
const received = Buffer.from(signature, 'hex');
return received.length === expected.length && timingSafeEqual(received, expected);
}

Pass your saved secret and the X-Kasbly-Signature header to the verifier. Reject a failed verification before processing the event. After verification succeeds, parse rawBody.toString('utf8') with JSON.parse. If you require authenticated deliveries, configure a secret and reject unsigned requests too.

Events delivered through the event bus use this envelope (illustrative values):

{
"event": "conversation.started",
"eventId": "evt_example",
"timestamp": "2026-10-19T12:00:00.000Z",
"data": {
"conversationId": "conv_example",
"customerId": "cust_example",
"channel": "WHATSAPP"
},
"tenantId": "tenant_example",
"entityType": "conversation",
"entityId": "conv_example"
}

event matches X-Kasbly-Event; eventId identifies the event, timestamp is its emission time, and data contains event-specific fields. tenantId identifies the workspace; entityType and entityId identify the affected record.

There is no universal envelope for all events. Directly dispatched events can put their fields at the top level. For example, survey.submitted sends:

{
"tenantId": "tenant_example",
"eventType": "survey.submitted",
"occurredAt": "2026-10-19T12:00:00.000Z",
"surveyId": "survey_example",
"conversationId": "conv_example",
"customerId": "cust_example",
"rating": 5,
"feedbackText": "Helpful support"
}

Order events can contain tenantId, timestamp, and order at the top level. Route by X-Kasbly-Event and handle the fields for that event instead of assuming every body contains data or eventId. The drawer’s examples describe the event fields; event-bus deliveries wrap those fields in data.

A Test Webhook send has its own body and is sent once, with X-Kasbly-Event: test and no retry header:

{
"event": "test",
"timestamp": "2026-10-19T12:00:00.000Z",
"message": "This is a test webhook from Kasbly"
}

Test sends use the same signing algorithm when a secret is supplied. A successful test checks connectivity and signature handling; exercise a selected real event to verify its payload handler too.

Return a 2xx response to acknowledge delivery. Automatic deliveries have a 10-second request timeout. Non-2xx responses, timeouts, and transient network failures trigger retries until the attempt limit is reached. URLs rejected by outbound security validation are not retried. Test sends have a 30-second timeout and are not automatically retried.

The Max Retries control (API field maxRetries) actually sets total attempts, including the initial request, from 1 to 10. The default is 3 attempts: the initial delivery, then up to 2 retries, delayed by 1 second and 3 seconds after the preceding failure. Setting it to 1 disables automatic retries.

For higher limits, successive retry delays are 1s, 3s, 10s, 30s, 60s, 120s, 300s, 600s, and 900s (up to 9 retries with 10 total attempts). These are backoff delays after failures, not exact times since the event; request duration and queue load add time. A manual retry queues one additional attempt.

Design the receiver to tolerate duplicate deliveries. Where an eventId is present, use it with the event type to deduplicate processing; other payload formats need an event-specific deduplication strategy. Save the event durably, then acknowledge promptly and perform slower work asynchronously. Inspect the subscription’s delivery logs to troubleshoot failures.