Skip to main content

Create Support Webhook

POST 

/api/support/webhooks

Registers an outbound webhook for the current tenant. Ticket events matching events are POSTed to url, signed with a secret that is minted here and returned exactly once, in this response — it is not shown on any later list, read or update, so store it now. Only a tenant admin can create webhooks.

Required scope: support:write

Grant this scope to your token under Settings → Developer → Personal Access Tokens.

Request body

  • url (string, required, max 2048) — an absolute URL that starts with https://. The host must resolve publicly: a URL whose host cannot be resolved, or resolves to a private or reserved IP range, is rejected with 422
  • events[] (array of strings, required, at least 1) — each one of: ticket.created, ticket.updated, ticket.replied, ticket.resolved. Unknown names are rejected; duplicates are collapsed
  • is_active (boolean, optional) — default true. Set false to register the endpoint without receiving deliveries yet

Webhook fields

  • id (integer)
  • tenant_id (string) — the owning tenant
  • url (string) — the HTTPS endpoint deliveries are POSTed to
  • events[] (array of strings) — subscribed events, each one of: ticket.created, ticket.updated, ticket.replied, ticket.resolved
  • is_active (boolean) — whether deliveries are queued for it; switched to false automatically after 20 consecutive failed deliveries
  • secret (string) — present only in the response to Create Support Webhook, never on a list, read or update. Store it when you create the webhook; it cannot be retrieved later (delete and re-create to rotate it)
  • created_by_user_id (integer, nullable) — who registered it
  • last_delivered_at (ISO-8601, nullable) — when a delivery last succeeded
  • failure_count (integer) — consecutive failed deliveries since the last success
  • created_at, updated_at (ISO-8601)

Delivery contract — what your endpoint must handle

Every delivery is an HTTPS POST to the webhook URL with a JSON body and these headers:

  • Content-Type: application/json
  • User-Agent: SKU.io-Support-Webhook/1.0
  • X-SKU-Event — the event name: ticket.created, ticket.updated, ticket.replied, ticket.resolved, or ping (test deliveries)
  • X-SKU-Delivery — the delivery id. Retries of the same delivery reuse the same id, so treat it as the idempotency key
  • X-SKU-Signaturesha256=<hex digest>: an HMAC-SHA256 of the raw request body, keyed with the webhook's secret. Verify it by computing the HMAC over the exact bytes you received (before any parsing or re-serialisation) and comparing with a constant-time comparison. Reject anything that does not match

Payload envelope (ticket events)

  • event (string) — same as X-SKU-Event
  • occurred_at (ISO-8601)
  • ticket (object) — the customer-facing ticket: id, number (#id), tenant_id, subject, type (nullable: bug, question, feature_request, billing), status (open, in_progress, waiting_on_customer, awaiting_release, resolved, closed), status_label (customer wording), priority (low, medium, high, urgent), source (app, email, api, assistant, agent, import), visibility (tenant, requester), tags[], requester (id nullable, name, email), assignee (id, name; nullable), created_at, updated_at, resolved_at (nullable), closed_at (nullable), url (path of the ticket in the app, e.g. /v2/support/2451)
  • actor (object, nullable) — who made the change: id, name; null for automated changes
  • change (object, nullable) — from / to values when the event carries a transition (e.g. a status change); otherwise null
  • message (object, nullable) — only on ticket.replied, and only when the reply is public: id, author (id, name), via (app, email, api, automation, ai, import, agent), body_text, created_at. Internal notes never leave the support desk

Ping envelope (Test Support Webhook): event = ping, occurred_at, and webhook (id, events[]) — no ticket.

What counts as success: any 2xx response within 15 seconds. A non-2xx status, a timeout, or a connection error is a failed attempt; the first 500 characters of a non-2xx body are kept in the delivery's error.

Retries and back-off: each ticket-event delivery is tried up to 3 times — immediately, then again after 30 seconds, then again after 5 minutes. The delivery stays pending between attempts (attempts, response_code and error reflect the latest try) and becomes delivered on the first 2xx or failed after the third miss. Test pings get a single attempt with no retry.

Endpoint health: every failed delivery adds one to the webhook's failure_count; a successful delivery resets it to 0 and stamps last_delivered_at. After 20 consecutive failed deliveries the webhook is switched off (is_active becomes false) and further events are not queued for it. Re-enabling it with Update Support Webhook resets failure_count to 0. A delivery to a webhook that is inactive at send time, or whose URL resolves to a private or reserved IP at send time, is marked failed without a request being made.

Which events fire

  • ticket.created — a ticket was opened (in the app, by email, through the API, or from the AI assistant)
  • ticket.replied — a public reply was posted (by the requester, a watcher, or an agent); the reply is in message
  • ticket.resolved — the status moved to resolved or closed
  • ticket.updated — any other customer-visible change: a status change to another state (including reopen), priority, type or visibility change, assignment or unassignment, a watcher added or removed, tags changed, a rating, a merge or split, or a customer-visible link added or removed (a release or the record the ticket was raised from). Internal notes, SLA bookkeeping, AI triage and agent-only links never fire a webhook

Authentication: Bearer token with the support:write scope, or an authenticated session. Customer endpoints run in tenant context — send the X-Tenant-Id header or call from a tenant subdomain.

Request

Responses

Created

Response Headers
    Content-Type