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.
support:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Request body
url(string, required, max 2048) — an absolute URL that starts withhttps://. The host must resolve publicly: a URL whose host cannot be resolved, or resolves to a private or reserved IP range, is rejected with 422events[](array of strings, required, at least 1) — each one of:ticket.created,ticket.updated,ticket.replied,ticket.resolved. Unknown names are rejected; duplicates are collapsedis_active(boolean, optional) — default true. Set false to register the endpoint without receiving deliveries yet
Webhook fields
id(integer)tenant_id(string) — the owning tenanturl(string) — the HTTPS endpoint deliveries are POSTed toevents[](array of strings) — subscribed events, each one of:ticket.created,ticket.updated,ticket.replied,ticket.resolvedis_active(boolean) — whether deliveries are queued for it; switched to false automatically after 20 consecutive failed deliveriessecret(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 itlast_delivered_at(ISO-8601, nullable) — when a delivery last succeededfailure_count(integer) — consecutive failed deliveries since the last successcreated_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/jsonUser-Agent: SKU.io-Support-Webhook/1.0X-SKU-Event— the event name:ticket.created,ticket.updated,ticket.replied,ticket.resolved, orping(test deliveries)X-SKU-Delivery— the delivery id. Retries of the same delivery reuse the same id, so treat it as the idempotency keyX-SKU-Signature—sha256=<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 asX-SKU-Eventoccurred_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(idnullable,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 changeschange(object, nullable) —from/tovalues when the event carries a transition (e.g. a status change); otherwise nullmessage(object, nullable) — only onticket.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 inmessageticket.resolved— the status moved toresolvedorclosedticket.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
- 201
- 401
- 403
- 422
- 429
Created
Response Headers
Unauthorized
Response Headers
Forbidden
Response Headers
Unprocessable Content
Response Headers
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.