Skip to main content
Specter sends a webhook for every decision to each endpoint subscribed to that event type. Webhooks are how you receive the final outcome of an asynchronous decision, and they let other systems react to decisions without polling. Register an endpoint with its URL and the events it subscribes to (see Register webhook). Every subscribed endpoint receives its own copy of each event.

Events

A provisional decision is normally finalized within 30 seconds. If the asynchronous backends have not answered by then, Specter fails open: it finalizes the decision as ALLOW and sends decision.async.allow. Simulations do not send webhooks.

Payload

Each notification is a POST request with a JSON body:
See Event notification for the full schema.

Responding to Webhooks

Specter counts a delivery as successful when your endpoint answers with any 2xx status code, for example 200, 202 or 204, within 15 seconds.
  • Acknowledge first, process later. Store the event and return 2xx right away, then do the work, such as capturing or cancelling a payment, asynchronously. A slow response counts as failed and the event is sent again.
  • Keep the response body empty. Specter ignores the body. If you do return one with content-type: application/json, it must be valid JSON; otherwise the delivery counts as failed.
  • Every other outcome is a failure. Any status outside 2xx, including 4xx, a timeout, and a connection or TLS error, causes a retry. There is no status code that stops retries.
  • Register the final URL. Specter follows redirects, but a 301, 302 or 303 redirect turns the request into a GET without the payload.
Use a publicly reachable HTTPS URL. Specter does not deliver to private or reserved IP addresses; such deliveries fail and are retried like any other failed delivery.

Retries

Specter makes up to five attempts per event and endpoint. After a failed attempt, it waits before trying again: Each wait can be up to 10% longer so retries don’t arrive in lockstep. The last attempt happens about a minute and a half after the first. If the fifth attempt fails, Specter drops the event. The endpoint stays active for later events, but failed deliveries cannot be listed or sent again. To recover a missed outcome, fetch the decision with Get decision details. Updating or deleting an endpoint affects only events that occur afterwards. Deliveries that are already queued, including their retries, still go to the previous URL.

Duplicates and Ordering

The same event can arrive more than once, for example when your endpoint processed it but answered too late. Retries carry the same id, the same body and the same signature, so store the id of each processed event and skip events you have already seen. Events are not guaranteed to arrive in order:
  • A decision.async.* event can arrive before the decision.provisional event of the same decision.
  • It can even arrive before Specter’s response to your POST /api/decisions request, when the backends answer quickly.
Track decisions by decision_id and treat a decision.async.* event as the final outcome, whenever it arrives. A decision.provisional event never replaces it.

Verifying Signatures

Every notification carries an x-hmac-signature header: the HMAC-SHA256 of the raw request body, encoded as lowercase hex. Specter generates the HMAC key when you register the endpoint and returns it only once, as hmac_key in the creation response. Store it securely; later responses show only a masked version. The key cannot be changed: to rotate it, register a new endpoint and then delete the old one. Use the key exactly as returned, as a string. It looks like base64, but must not be decoded. To verify a notification:
  1. Calculate the HMAC-SHA256 of the raw request body, before any JSON parsing, with your hmac_key.
  2. Compare the result with the x-hmac-signature header, using a constant-time comparison.
If they match, the notification was sent by Specter and was not modified in transmission.

Example

For the HMAC key q3Vt8hZ2mN0xYcLr5pW7sKfA1eJ9uDbG4oTnXiRvEyQ and this raw body:
the x-hmac-signature header is:
Verification in Node.js:
Pass the body exactly as received, for example as a Buffer from a raw body parser. Re-serializing parsed JSON can change the bytes and break the signature.