Support Outbound Webhooks

Queue and verify signed Support ticket-event deliveries.

Support webhooks enqueue ticket events for delivery to an HTTPS endpoint.

Delivery is asynchronous and can take up to 24 hours on the current service tier. Use the test-delivery action for immediate endpoint validation.

Configure

An Admin or Owner can open Support > Settings > Webhooks, add an HTTPS URL, choose events, enable the webhook, and copy its generated secret.

The current event registry contains:

  • ticket.created
  • ticket.updated
  • ticket.field_changed
  • ticket.status_changed
  • ticket.priority_changed
  • ticket.assigned
  • ticket.label_added
  • ticket.label_removed
  • ticket.customer_replied
  • ticket.agent_responded
  • ticket.escalated
  • ticket.resolved
  • ticket.closed
  • ticket.deleted
  • ticket.sla_warning
  • ticket.sla_breached
  • ticket.reply_scheduled
  • ticket.scheduled_reply_sent
  • ticket.scheduled_reply_cancelled
  • webhook.test

Event producers are implemented across different write paths. Before relying on an event for a test, perform its matching UI/API action and confirm a delivery row appears.

Delivery headers

Leenops sends:

X-Leenops-Signature: sha256=<hex>
X-Leenops-Timestamp: <unix-seconds>
X-Leenops-Event: <event-name>
X-Leenops-Delivery-Id: <delivery-uuid>
X-Leenops-Module: support

The signed message is:

<timestamp-seconds>.<raw-json-body>

Verify the HMAC-SHA256 using the webhook secret and compare in constant time:

import crypto from "node:crypto";

export function verifyLeenopsWebhook(rawBody, headers, secret) {
  const timestamp = headers["x-leenops-timestamp"];
  const supplied = headers["x-leenops-signature"];
  const signed = `${timestamp}.${rawBody}`;
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(signed).digest("hex");

  if (!supplied || supplied.length !== expected.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(supplied),
    Buffer.from(expected),
  );
}

Reject stale timestamps (five minutes is a reasonable replay window) and deduplicate on X-Leenops-Delivery-Id.

Success, failure, and retries

  • 2xx: delivered.
  • 429, 5xx, network error, or 10-second timeout: retryable.
  • Other 4xx: permanent failure.
  • Maximum: four attempts total.
  • Stored retry offsets: 1, 5, and 30 minutes after successive failures.

Because the production drain is daily, those minute-level due times do not guarantee minute-level retry delivery; a due row waits for the next worker run.

Testing

  1. Create and enable a webhook in a disposable workspace.
  2. copy the secret.
  3. use Send test and verify webhook.test.
  4. verify all five Leenops headers and the timestamp-prefixed signature.
  5. return a 2xx within 10 seconds and process asynchronously.
  6. test deduplication using the delivery ID.
  7. inspect delivery history for status and errors.