Webhooks

Event delivery signed with the same key set as verdicts — verify a webhook the way you verify an approval.

Webhooks tell your systems what happened without polling. Delivery is signed the same way verdicts are: every event arrives as an ES256 JWT, verifiable against the public key set at verdicts.saifuro.com/.well-known/jwks.json. One verification routine covers both, and you never have to take an inbound request's word for where it came from — there is no shared webhook secret to store or leak.

Register an endpoint

POST/v1/webhooks
FieldTypeRequiredMeaning
urlstringyesHTTPS endpoint on your side
eventsarrayyesWhich event types to deliver, from the table below
{
  "webhook": "whk_4b09",
  "url": "https://ops.example.com/saifuro",
  "events": ["decision.review_pending", "escrow.disputed"],
  "status": "active",
  "created_at": "2026-09-01T10:00:00Z"
}

GET /v1/webhooks, GET /v1/webhooks/{id}, and DELETE /v1/webhooks/{id} complete the surface. POST /v1/webhooks/{id}/ping sends a ping event to test the path end to end.

Delivery

An event is an HTTP POST to your URL with Content-Type: application/jwt and the token as the body. Decoded, the claims are:

Eventescrow.disputedsigned JWTYour endpointContent-Type: application/jwt2xxacknowledged, doneanything elseretry with backoffRetries continue for 24 hours. After that the event is dropped from delivery and stays readable in the log.The event id is stable across retries, so a duplicate is detectable by an id you have already processed.
{
  "iss": "https://verdicts.saifuro.com",
  "jti": "evt_a91c",
  "iat": 1788258300,
  "type": "escrow.disputed",
  "data": {
    "escrow": "esc_2f8d",
    "state": "funded",
    "disputed": true
  }
}

Verify it in the same three steps as a verdict — signature against the JWKS, issuer, and then look at the claims. jti is the event id and stays the same across retries, so a duplicate is detectable by the id you have already processed.

Respond 2xx to acknowledge. Anything else, and delivery retries with backoff for 24 hours, after which the event is dropped from delivery but stays readable in the log.

data carries the object the event is about, in the same shape the API returns it — enough to act on, and the id to fetch the rest.

Event types

TypeFires when
decision.review_pendingA verdict came back review and waits for the mandate owner
decision.resolvedA review was approved, denied, or expired
mandate.revokedA mandate was revoked; the next request under it returns deny_revoked
mandate.expiringSeven days before a mandate's expires date
escrow.fundedBuyer's funds locked in the contract
escrow.deliveredThe condition was attested
escrow.releasedFunds moved to the seller
escrow.refundedFunds returned to the buyer
escrow.disputedA party disputed the deal — see escrow
pingYou asked for one

Events describe state that already changed. Missing a webhook loses you a notification, never a record: everything here is reconstructable from decisions and exports.

On this page