Skip to content
Webhooks

Subscribe an endpoint to a business's domain events and Sessions delivers each one as a signed HTTP POST. Use them to sync registrations, purchases, and opt-ins into your own systems in near real time.

Create and manage endpoints in the business admin under Settings → Webhooks: add a URL, choose which events to receive, and reveal or rotate the signing secret.

Events

The event types you can subscribe to today. The key is what arrives in the payload's `type` field.

participant.marketing_consent_granted

A participant opted in to email marketing.

activity_registration.confirmed

A participant's registration for an activity was confirmed.

product_purchase.completed

A product purchase completed.

Payload

Every delivery is a JSON envelope. `id` is the event id — stable across retries, so you can dedupe on it.

{
  "id": "evt_8f2c1a09b3",
  "type": "participant.marketing_consent_granted",
  "apiVersion": "2026-06-01",
  "occurredAt": "2026-06-01T14:32:08.000Z",
  "resourceReferences": [
    "gid://Sessions/BusinessParticipant/p_91ac"
  ],
  "data": {
    "id": "gid://Sessions/BusinessParticipant/p_91ac",
    "name": "Jordan Vega",
    "email": "jordan@example.com",
    "emailMarketingConsent": {
      "status": "granted",
      "at": "2026-06-01T14:32:08.000Z"
    }
  }
}

`data` is the event's resource as a minimized node — the same field names and opaque GIDs the GraphQL API uses, with a reduced field set. Take the GID and re-query the API for the full object. `previousAttributes` is included only for events that change existing state.

Verifying signatures

Every request carries these headers:

Sessions-Signature: t=<unix-seconds>,v1=<hex HMAC-SHA256>
Sessions-Event-Type: activity_registration.confirmed
Sessions-Webhook-Id: <per-delivery id>

The signature is HMAC-SHA256 over "<timestamp>.<rawBody>" using the endpoint's signing secret, hex-encoded. Recompute it over the exact bytes received and compare in constant time.

import {createHmac, timingSafeEqual} from 'node:crypto';

// Express example. `req.rawBody` must be the exact bytes we sent —
// verify before any JSON parsing re-serializes them.
function verify(req, signingSecret) {
  const header = req.get('Sessions-Signature'); // "t=1717250000,v1=abc123…"
  const parts = Object.fromEntries(
    header.split(',').map((kv) => kv.split('=')),
  );

  const signed = `${parts.t}.${req.rawBody}`;
  const expected = createHmac('sha256', signingSecret)
    .update(signed)
    .digest('hex');

  const ok = timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1 ?? ''),
  );

  // Reject stale timestamps to bound replay (e.g. 5-minute tolerance).
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return ok && fresh;
}

Retries & delivery

Respond with any 2xx status to acknowledge. A non-2xx, a timeout, or a network error is retried with escalating backoff (1m, 5m, 30m, 2h, 6h).

After six failed attempts a delivery is dead-lettered and no longer retried. Re-enable or fix the endpoint and future events will deliver normally.

Webhooks