Outbound webhooks

Outbound webhooks let your own systems stay in sync with OpenContact in real time, instead of polling the API. Subscribe a URL to the events you care about, and OpenContact POSTs to it as they happen.

Subscribing

Create a subscription on the Developers page (or via the API): enter your URL, pick the events, and copy the signing secret — it’s shown once. You can subscribe to:

  • contact.created
  • contact.status_changed
  • contact.unsubscribed
  • contact.bounced
  • contact.complained

The payload

Each delivery is a POST with a JSON body:

{
  "id": "evt_9f8c…",
  "event": "contact.created",
  "occurred_at": "2026-07-27T10:00:00+00:00",
  "data": { "id": 42, "email": "ana@example.com", "first_name": "Ana", "last_name": "Ng", "status": "subscribed" }
}

The id is stable across retries of the same event — store it and skip anything you’ve already processed (idempotency). It’s also sent in the X-EmailRoute-Delivery header.

Verifying a delivery is genuine

Every request carries:

  • X-EmailRoute-Event — the event type
  • X-EmailRoute-Delivery — the delivery id (matches id in the body)
  • X-EmailRoute-Signature — t=<unix_timestamp>,v1=<hmac>, where the HMAC-SHA256 is keyed with your signing secret over the string "{timestamp}.{raw_body}"

Parse the header, reject anything outside a tolerance window (guards against replay), then compare the signature in constant time:

[$t, $v1] = [null, null];
foreach (explode(',', $request->header('X-EmailRoute-Signature')) as $part) {
    [$k, $v] = explode('=', $part, 2);
    if ($k === 't') $t = $v;
    if ($k === 'v1') $v1 = $v;
}

abort_if(abs(time() - (int) $t) > 300, 401);            // 5-minute tolerance
$expected = hash_hmac('sha256', $t.'.'.$rawBody, $secret);
abort_unless(hash_equals($expected, (string) $v1), 401); // true = authentic

Only act on a request whose signature matches.

Reliability

Deliveries retry with exponential backoff if your endpoint returns a non-2xx response, so a brief outage on your side won’t drop events. The dashboard shows each subscription’s recent deliveries (status and response code), and a Send test button posts a sample event so you can validate a new endpoint before real traffic flows.

Common uses

  • Mirror suppression into your own database the instant it happens.
  • Kick off onboarding in your app when a lead is created.
  • Feed a data warehouse or analytics pipeline.

Still stuck?

Send us your project ID and what you saw. Contact support.