Skip to main content
When a verification is submitted, or a gate check stops it, Privue posts a callback to an endpoint registered against its workflow. It tells you which verification moved; you read the record with GET /verifications/{verification_id} as usual. On a hosted workflow the submission is the user’s; where you supply the documents it is the run of the checks finishing. Registering an endpoint is optional, and it is done per workflow. Each of your journeys posts to its own endpoint or to none, so a uat workflow reports to wherever you are testing and a production one to where your real work goes. A workflow with no endpoint registered delivers nothing, and you poll its verifications instead.

What earns a callback

verification.submitted and verification.gated are the events we post, so they are the transitions you can be told about: Every other status is read rather than delivered: poll GET /verifications/{verification_id} for one record, or list and count them across your account with Tracking verifications.

Registering an endpoint

Tell us which workflow, the URL to post to, and how a delivery should identify itself. The URL must be absolute and https, with no credentials and no fragment. Registration belongs to the workflow, so publishing a new version of a journey leaves its endpoint exactly as it stands, and changing an endpoint needs no change to the journey. Registering one workflow says nothing about any of your others. You choose one of two ways to be sure a delivery is ours, whichever fits the endpoint you already run. A secret you choose. Pick a long random string and Privue sends it back in a header on every delivery, X-Privue-Secret by default or one you name. Keep it wherever your handler can read it, and tell Privue to change it whenever you want. A token from your own auth service. If your endpoint only accepts tokens it issued, give Privue the URL of the service that issues them, the credentials to present, any header it needs to route the request, and where the token sits in its answer. Privue posts those credentials to it and carries the token it returns on the delivery, as Authorization: Bearer <token> by default or in a header and with a prefix you name. A fresh token is fetched for each delivery, immediately before it is sent, so nothing we hold can go stale between the two and every delivery arrives with a token your service issued seconds earlier.

The payload

The callback carries no step data. A verification is only submitted with every step settled, and a gated one has ended where the check stopped it, so the record is final the moment you are told about it: read it with GET /verifications/{verification_id} and you have the whole result, with gated_by_step naming the check on a gated one.

Checking it came from us

Every request carries X-Privue-Event, the event name and the same value as event in the body. A gate check posts to your own system the same way and opens with the same fields up to reference_user_id, under the event verification.check. Whatever proves the delivery is ours travels beside it, in the form you registered. On a secret you chose, reject anything whose header does not match it, and compare the two with your language’s constant-time function - hmac.compare_digest in Python, crypto.timingSafeEqual in Node - rather than ==. The secret travels in the request, so treat it like any other credential: keep it out of logs, and ask us to change it if it is ever exposed. On a token from your own auth service, check it exactly as your other endpoints check one. Answer 401 or 403 to a token you will not accept and Privue stops rather than retrying, because a credential your own service rejects will be rejected again. Serve the endpoint over https only, either way.

Responding, retries, and repeats

Answer 2xx within 10 seconds. Do the least you can before answering: record the verification_id and return, then read the record on your own time. A handler that reads the record and runs your onboarding before answering is a handler that times out and gets the same callback again. A timeout, a refused connection, and a 5xx are all retried with exponential backoff: by default 12 attempts, the first repeat 5 seconds after the first attempt, each pause twice the last and none longer than 30 minutes, which comes to about two hours. The retries are part of the endpoint’s registration, so ask us for a different number of attempts, first pause, multiplier or longest pause where yours needs one, up to 20 attempts and an hour between any two. The record waits for them before it completes. An answer that says the request itself was wrong - 400, 401, 403, 404, 405, 410, 415, 422 - is not retried, because repeating it would get the same answer. Deliveries can repeat. A response that is lost on the way back looks identical to one that never arrived, so a callback you already handled can be delivered a second time. Make your handler idempotent, keyed on verification_id and occurred_at together: a repeat of one delivery carries both unchanged. Key on the pair rather than on verification_id alone, because a verification you reset and that is submitted or gated again is a second run with a second result, and it earns a callback of its own. Its occurred_at is the moment that run ended, so it tells the two apart.

When a callback cannot be delivered

The verification completes anyway, and a gated one stays gated. The result was final before you were told anything, so an endpoint that is down holds nothing back, and the record says what happened to the callback:
status is delivered or failed, settled_at is when the callback was accepted or when the attempts ran out, and reason says why nothing was accepted on a failed one. The field is null until the callback settles, and on every verification of a workflow with no endpoint registered. A reset clears it along with the rest of the journey: it described the ending the reset undid. So a run of failed callbacks is recoverable without us: list the records for the period your endpoint was down, and callback.status tells you which of them you were never told about. See Catching up on callbacks you missed.