Skip to content

Webhooks ​

Webhooks push things that happen in a workspace to your own HTTP endpoint, so you can react without polling.

Register an endpoint ​

Create one with POST /api/webhooks (token scope webhooks:write) or in the app:

sh
curl https://sphericon.example.com/api/webhooks \
  -H "Authorization: Bearer $SPHERICON_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://app.example.com/hooks/sphericon","eventTypes":["email.bounced","email.complained"]}'

eventTypes filters by event name; leave it empty to receive everything. enabled: false pauses an endpoint without deleting it. The URL must be an absolute http or https address.

The signing secret is never returned by the API. Read it in the app, where the endpoint is shown.

Events ​

EventWhen
contact.createdA new contact is created.
email.sentA message was handed to the transport.
email.openedA recipient opened a message.
email.clickedA recipient clicked a tracked link.
email.unsubscribedA recipient unsubscribed.
email.bouncedA message hard-bounced.
email.complainedA recipient reported a message as spam.
marketing.confirmedA contact confirmed their double opt-in.
your own actionsEvery event you record, under the action name used.

Events you send through /api/events or the tracker are delivered under their own action name, for example page_view or signed_up, so an endpoint can subscribe to exactly the ones it needs.

Payload ​

Each delivery is a POST with a JSON body:

json
{
  "id": "0b6f3f0e-…",
  "type": "email.clicked",
  "occurredAt": "2026-10-09T12:30:00Z",
  "workspaceId": 7,
  "subject": "ada@example.com",
  "contactId": 42,
  "data": {}
}

data carries the details specific to the event type (for example the message and link for a click, or the properties of a custom event). contactId is absent for events with no contact yet, such as an anonymous page view.

Headers:

HeaderMeaning
webhook-idUnique delivery id. Use it to deduplicate.
webhook-timestampUnix seconds when the delivery was signed.
webhook-signatureHMAC signature of the body.
X-sphericon-EventThe event name, for routing before you parse.

Verify the signature ​

Signatures follow the Standard Webhooks scheme, so any of its libraries verifies a delivery: pass it the raw request body, the three webhook-* headers and your endpoint secret. Reject the request if verification fails, and use the timestamp to refuse stale replays.

Delivery and retries ​

  • Respond with any 2xx status to acknowledge. Anything else, or a connection failure, is retried (up to 10 attempts), so your handler may see the same delivery more than once. Deduplicate on webhook-id.
  • Redirects are not followed; a 3xx counts as a failure.
  • Endpoints that resolve to private, loopback, link-local or cloud-metadata addresses are refused, so you cannot point a webhook at an internal service. Use a public URL, or a tunnel for local development.
  • Deliveries run on the same durable PostgreSQL job queue as sending, so a restart does not lose them. There is no ordering guarantee between events.