Skip to content
Last updated

Webhooks

Karzoun webhooks deliver real-time HTTP notifications when something changes in your workspace. Register an HTTPS endpoint, pick the events you care about, and Karzoun POSTs a structured JSON payload whenever they fire — for example when a conversation is assigned, a customer is updated, or a WhatsApp flow completes.

Use webhooks to sync CRM data, trigger automations in your stack, notify external systems, or feed event streams into your own analytics pipeline.

MiniApp vs tenant webhooks

This guide covers outbound webhooks (Karzoun notifies your server). For inbound provider webhooks configured inside MiniApp JSON, see MiniApps webhooks.

Where to manage webhooks

Create and inspect subscriptions in Developer → Webhooks (/developer/webhooks) or via GraphQL mutations. Search the GraphQL API reference for Webhook, webhooksAdd, and related operations.

How delivery works

When an event occurs, Karzoun:

  1. Finds every active webhook subscribed to that type + action pair
  2. Builds a versioned JSON envelope (see Payload format)
  3. Signs the raw body and attaches auth headers
  4. POSTs to your URL with up to 3 retry attempts on failure
  5. Records every attempt in delivery logs for debugging

Your endpoint should return any 2xx status within the connection timeout. Non-2xx responses and network errors trigger automatic retries with exponential backoff.


Security

Karzoun secures webhook traffic with two independent mechanisms on every delivery. Use one or both to confirm the request genuinely came from Karzoun.

MechanismHeaderPurpose
TokenKarzoun-tokenStatic bearer token generated when the webhook is created
HMAC signatureX-Karzoun-Signature-256sha256= + HMAC-SHA256 of the raw request body using your webhook secret

During a secret rotation grace period (24 hours), Karzoun also sends X-Karzoun-Signature-256-Previous signed with the previous secret so you can roll over without downtime.

Reject unauthenticated requests

Always verify the token or signature before processing a payload. If neither matches, respond with 401 and discard the body.

Verify the HMAC signature

The signature header value is prefixed with sha256=. Strip that prefix, then compare the remainder to an HMAC you compute over the exact raw body bytes (not a re-serialized JSON object).

Node.js

const crypto = require('crypto');

function verifyKarzounWebhook(req, secret) {
  const header = req.headers['x-karzoun-signature-256'];
  if (!header || !header.startsWith('sha256=')) return false;

  const expected = header.slice('sha256='.length);
  const computed = crypto
    .createHmac('sha256', secret)
    .update(req.rawBody, 'utf8') // use the raw body string/buffer
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(computed, 'hex'),
  );
}

PHP

$secret = getenv('WEBHOOK_SECRET');
$header = $_SERVER['HTTP_X_KARZOUN_SIGNATURE_256'] ?? '';
$body = file_get_contents('php://input');

if (!str_starts_with($header, 'sha256=')) {
  http_response_code(401);
  exit;
}

$expected = substr($header, 7);
$computed = hash_hmac('sha256', $body, $secret);

if (!hash_equals($expected, $computed)) {
  http_response_code(401);
  exit;
}

http_response_code(200);

Verify the token

Compare the Karzoun-token header to the token returned when you created the webhook. This is a simple shared-secret check — pair it with HMAC verification for defense in depth.


Register a webhook

Register subscriptions with webhooksAdd. Karzoun generates a unique token and HMAC secret, validates your URL (HTTPS + SSRF checks), and sends an automatic connectivity ping.

ParameterTypeDescription
urlstringRequired. HTTPS endpoint that receives POST requests
namestringHuman-readable label
descriptionstringWhat this webhook is used for
actionsarrayEvent subscriptions — each entry has type, action, and optional label
retryPolicy.maxAttemptsintMax delivery attempts (default: 3)
retryPolicy.backoffMsint[]Delay between retries in ms (default: 5000, 30000, 120000)
rateLimitPerMinuteintMax deliveries per minute per webhook (default: 100)
Discover available events

Query webhooksGetActions to list every event your workspace can subscribe to. The catalog below reflects the current platform modules.

GraphQL

mutation {
  webhooksAdd(
    url: "https://api.example.com/karzoun/events"
    name: "CRM + inbox sync"
    description: "Customer and conversation updates"
    actions: [
      { type: "core:customer", action: "create" }
      { type: "core:customer", action: "update" }
      { type: "inbox:conversation", action: "create" }
      { type: "inbox:conversation", action: "assign" }
      { type: "whatsapp:flow", action: "completed" }
    ]
    retryPolicy: {
      maxAttempts: 3
      backoffMs: [5000, 30000, 120000]
    }
  ) {
    _id
    url
    token
    secret
    actions { type action label }
    retryPolicy { maxAttempts backoffMs }
  }
}

cURL

curl -X POST 'https://YOUR_SUBDOMAIN.api.karzoun.chat/graphql' \
  -H 'Content-Type: application/json' \
  -H 'x-app-token: YOUR_APP_TOKEN' \
  -d '{
    "query": "mutation { webhooksAdd(url: \"https://api.example.com/karzoun/events\", name: \"My hook\", actions: [{ type: \"core:customer\", action: \"update\" }]) { _id token secret } }"
  }'

Store credentials immediately. The token and secret are returned on creation. Use webhooksRotateSecret to roll the HMAC secret; the previous secret remains valid for 24 hours.

Other management operations

MutationDescription
webhooksEditUpdate URL, name, actions, retry policy, or rate limit
webhooksRemoveDelete a webhook and its delivery logs
webhooksTogglePause or resume deliveries without deleting config
webhooksRotateSecretGenerate a new HMAC secret (24h grace on the old one)
webhooksPingSend a test system.ping event to verify connectivity
webhooksRetryDeliveryManually retry a failed delivery log entry
webhooksResetReset circuit breaker and error counters after fixing your endpoint
webhookDeliveryLogsInspect request/response details for debugging

Payload format

Every delivery uses the same envelope shape (version is currently 1):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "version": "1",
  "timestamp": "2026-06-23T14:30:00.000Z",
  "event": "inbox:conversation.create",
  "data": {
    "_id": "abc123",
    "status": "open",
    "customerId": "cust_456"
  },
  "metadata": {
    "webhookId": "wh_789",
    "deliveryId": "del_012",
    "attemptNumber": 1,
    "description": "Customer has started a new conversation",
    "resourceUrl": "https://YOUR_SUBDOMAIN.karzoun.chat/inbox?conversation=abc123"
  }
}
FieldDescription
idUnique event ID — use as an idempotency key to deduplicate retries
eventFully-qualified name: <type>.<action> (e.g. core:customer.update)
dataNative JSON object for the affected resource — never double-stringified
metadata.attemptNumberWhich delivery attempt this is (1 = first try)
metadata.resourceUrlDeep link to the resource in the Karzoun UI

For delete events, data is normalized to { "type": "<type>", "object": { "_id": "..." } }.

Connectivity pings use event: "system.ping" with a short test message in data.


Retries, timeouts, and circuit breaker

SettingDefaultBehavior
HTTP timeout10 secondsKarzoun waits up to 10s for your server to respond
Max attempts3Failed deliveries retry with backoff: 5s → 30s → 2min
Circuit breaker5 failuresAfter 5 consecutive failures, deliveries pause until cooldown (5 min) or you call webhooksReset
Rate limit100/minPer-webhook cap; excess events are logged as skipped
Respond quickly

Return 2xx as soon as you have accepted the payload. Offload heavy work to a background queue — slow handlers risk timeouts and retries.

Idempotent handlers

The same logical event may arrive more than once (id stays the same across retries; metadata.deliveryId changes per attempt). Design handlers to tolerate duplicates.


Event catalog

Events are identified by a type (module + resource) and action (what happened). In the delivered payload they appear as event: "<type>.<action>".

Query webhooksGetActions for the live list. Subscriptions below are grouped by platform module.

Customers (core:customer)

EventactionDescription
core:customer.createcreateA customer record was created
core:customer.updateupdateA customer record was updated
core:customer.deletedeleteA customer record was deleted
core:customer.mergemergeTwo customer records were merged

Conversations & inbox (inbox:*)

EventactionDescription
inbox:conversation.createcreateA new conversation started
inbox:conversation.statusstatusConversation status changed (e.g. open → closed)
inbox:conversation.assignassignConversation assigned to an agent
inbox:conversation.unassignunassignConversation unassigned
inbox:conversation.updateupdateConversation metadata updated
inbox:feedback.createcreateCustomer submitted feedback
inbox:summary.createcreateAI conversation summary generated
inbox:popupSubmitted.createcreateCustomer submitted a popup form

WhatsApp (whatsapp:*)

EventactionDescription
whatsapp:order.receivedreceivedOrder received via WhatsApp commerce
whatsapp:flow.completedcompletedWhatsApp Flow finished by the customer

Tasks (tasks:*)

EventactionDescription
tasks:task.createcreateTask created
tasks:task.updateupdateTask updated
tasks:task.deletedeleteTask deleted
tasks:task.assignassignTask assigned
tasks:task.stage_changestage_changeTask moved to a different stage
tasks:task.priority_changepriority_changeTask priority changed
tasks:task.due_datedue_dateTask due date changed
tasks:checklist.item_checkeditem_checkedChecklist item marked complete

Meetings (meetings:meeting)

EventactionDescription
meetings:meeting.createcreateMeeting scheduled
meetings:meeting.updateupdateMeeting details changed
meetings:meeting.cancelcancelMeeting cancelled
meetings:meeting.startstartMeeting started

Timeclock (timeclock:*)

EventactionDescription
timeclock:shift.clock_inclock_inEmployee clocked in
timeclock:shift.clock_outclock_outEmployee clocked out
timeclock:absence.requestedrequestedAbsence / time-off requested
timeclock:absence.approvedapprovedAbsence request approved
timeclock:absence.rejectedrejectedAbsence request rejected
timeclock:schedule.submittedsubmittedSchedule submitted for approval
timeclock:schedule.approvedapprovedSchedule approved
timeclock:schedule.rejectedrejectedSchedule rejected

System

EventactionDescription
system.pingpingConnectivity test sent by webhooksPing or on webhook creation

Troubleshooting

1. Confirm Karzoun is sending events

  • Open Developer → Webhooks → Delivery logs for the subscription
  • Run webhooksPing and check for a system.ping delivery with status success
  • Temporarily point the URL to webhook.site to inspect raw requests

2. Check your endpoint

SymptomLikely cause
No requests at allWebhook paused (isActive: false), circuit open, or wrong event subscription
401 on your sideToken/signature verification failing — compare raw body, not parsed JSON
TimeoutsHandler too slow; Karzoun aborts after 10 seconds
circuit-open statusFive consecutive failures — fix the endpoint, then webhooksReset
Skipped deliveriesRate limit exceeded for that webhook

3. Validate URL requirements

  • Must be HTTPS
  • Must resolve to a public IP (private ranges and localhost are blocked for SSRF protection)
  • Must return 2xx for Karzoun to consider delivery successful

Next steps