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.
Request headers
Section titled “Request headers”| 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).
Verify the signature before parsing JSON
Section titled “Verify the signature before parsing JSON”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.
Payload formats
Section titled “Payload formats”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.
Delivery and retries
Section titled “Delivery and retries”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.
