Skip to main content
Webhooks push events to a URL you control instead of you polling for them — the server-to-server equivalent of the discovery event stream for callers that run batch jobs or don’t hold a connection open. Register a subscription once; every matching event after that is delivered as an HTTP POST to your endpoint, HMAC-signed so you can verify it came from us.

Register a subscription

POST /api/v2/buyer/webhook-subscriptions
curl
curl -X POST "https://api.interchange.io/api/v2/buyer/webhook-subscriptions" \
  -H "Authorization: Bearer $SCOPE3_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/scope3",
    "secret": "a-secret-at-least-16-characters-long",
    "eventTypes": ["discovery.revision"]
  }'
FieldTypeRequiredNotes
urlstringYesHTTPS endpoint we POST events to
secretstringYes16–256 characters. Used to compute the X-Webhook-Signature header — keep it private
eventTypesarrayYesOne or more of the event catalog below
Response (201):
{
  "data": {
    "subscription": {
      "id": "wh_1751000000000_a1b2c3d4",
      "url": "https://example.com/webhooks/scope3",
      "eventTypes": ["discovery.revision"],
      "status": "active",
      "failureCount": 0,
      "lastSuccess": null,
      "lastFailure": null,
      "createdAt": "2026-07-06T00:00:00.000Z",
      "updatedAt": "2026-07-06T00:00:00.000Z"
    }
  }
}
The response never echoes your secret back — store it when you register.

List subscriptions

GET /api/v2/buyer/webhook-subscriptions Returns every subscription for your account, newest first. Same subscription shape as above, minus the secret.

Delete a subscription

DELETE /api/v2/buyer/webhook-subscriptions/{id} Deliveries stop immediately. Returns 404 if the id doesn’t exist or belongs to another account.

Verifying deliveries

Every delivery carries an X-Webhook-Signature header: the hex-encoded HMAC-SHA256 of the exact request body, keyed with your subscription’s secret. Recompute it and compare before trusting the payload:
import crypto from 'node:crypto'

function isValidSignature(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
}
Compute the HMAC over the raw request body, not a re-serialized copy — key order or whitespace differences will change the signature.

Retry semantics

A delivery is a single POST attempt per event. If your endpoint doesn’t respond with a 2xx status, the attempt counts as a failure — it is not retried inline, since push events like discovery.revision are time-sensitive and a delayed retry would deliver stale state. Instead:
  • Each failure increments failureCount on the subscription.
  • After 10 consecutive failures, the subscription’s status flips to failed and delivery stops. List your subscriptions to check status, failureCount, and lastFailure, then delete and re-register once your endpoint is healthy again.
  • A successful delivery resets failureCount to zero.
Because delivery isn’t retried, treat webhooks as an optimization, not your only source of truth: an endpoint that’s been down can catch up by polling the equivalent REST resource (for discovery.revision, that’s Browse products with sinceRevision).

Event catalog

EventFires whenPayload
discovery.revisionA progressive discovery session’s snapshot advances — the same moment the SSE stream emits a revision or complete frame{ event, discoveryId, revision, resultsComplete, productGroups, pendingAgents, incompleteAgents, timestamp }
discovery.revision is one payload contract delivered over two transports: hold a connection open and use the SSE stream, or register a webhook if you’d rather not. The payload shape is identical either way — productGroups contains only groups that landed since the previous revision (replace-by-groupId semantics), resultsComplete: true marks the final event for that discovery session.
{
  "event": "discovery.revision",
  "discoveryId": "disc_01HZX3YQ7K9R6V3M2P1E0F8B2T",
  "revision": 2,
  "resultsComplete": false,
  "productGroups": [
    { "groupId": "ctx_disc-group-2", "groupName": "CTV Network", "products": ["..."] }
  ],
  "pendingAgents": [
    { "agentId": "agent_slow_media", "agentName": "Slow Media Storefront" }
  ],
  "timestamp": "2026-07-06T00:00:12.500Z"
}
Only subscriptions active before a discovery session starts receive that session’s events — there’s no replay of revisions that landed before you registered.

Errors

  • 401 UNAUTHORIZED — missing or invalid auth.
  • 400 VALIDATION_ERROR — invalid url, secret shorter than 16 characters, or empty eventTypes.
  • 404 NOT_FOUND — (delete only) no subscription with this id for your account.
See Errors for the full error contract.

Stream discovery events

The SSE-equivalent of discovery.revision for a held-open connection

Browse products

Poll for the same revisions if you’d rather not use push at all