Skip to content

Outbound webhooks

Verity posts to your endpoints when a review changes state. This is how you react to decisions programmatically — there is no write API for them.

Setting one up

Settings → Webhook endpoints, needing webhook_endpoint.manage. You provide a URL and select the events to subscribe to; Verity generates a signing secret.

Events

EventFired when
entity_review.approvedA review is approved
entity_review.rejectedA review is rejected
entity_review.escalatedA review is escalated
entity_review.escalated_to_amlA review is escalated specifically to AML
entity_review.assignedA review's assignee changes
entity_review.checks_queuedChecks are queued for a review

An endpoint receives only the events it is subscribed to, and only while it is active.

Payload

http
POST https://your-endpoint.example.com/verity
Content-Type: application/json
X-Verity-Signature: sha256=<hex digest>
json
{
  "event_type": "entity_review.approved",
  "entity_review_id": 7,
  "entity_id": 42,
  "account_id": 1,
  "outcome": "approved",
  "occurred_at": "2026-06-28T09:15:00Z"
}

The payload identifies what happened to what. It deliberately does not carry the entity's full state — fetch GET /api/v1/entity_reviews/:id for that, so you read the current truth rather than a snapshot that may already be stale.

Verifying the signature

X-Verity-Signature is sha256= followed by the HMAC-SHA256 hex digest of the raw request body, keyed with your endpoint's signing secret.

ruby
expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", signing_secret, request.raw_post)
ActiveSupport::SecurityUtils.secure_compare(expected, request.headers["X-Verity-Signature"])
python
import hmac, hashlib
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
hmac.compare_digest(expected, received_header)

Three rules:

  1. Compute over the raw body, before any JSON parsing or re-serialising
  2. Use a constant-time comparison
  3. Reject anything whose signature does not verify — do not process it and log the mismatch

Delivery semantics

  • Delivered by a background job, one per subscribed endpoint
  • Any 2xx counts as delivered; anything else is recorded as failed with the status and body
  • Attempts, timestamps and errors are visible under Webhook deliveries (webhook_delivery.view)

There is no automatic retry

A failed delivery stays failed until someone acts on it. Do not treat webhooks as a guaranteed stream: if your system must not miss a decision, reconcile periodically against GET /api/v1/entities. Deliveries are recorded, so a gap is visible rather than silent.

Endpoint requirements

  • HTTPS. Plain http works only where your network genuinely permits it, and you should not.
  • Respond quickly with 2xx and do the work asynchronously — a slow endpoint is a failed one.
  • Be idempotent. Handle the same entity_review_id and event_type arriving twice.

Inbound webhooks

Verity also receives webhooks — Focal posts screening results to POST /webhooks/focal. Those are HMAC-authenticated and fail closed: if no secret is configured, the request is rejected rather than trusted. That is provider configuration, not something you set up as a consumer.