Skip to content

Receiving webhooks

The webhook is the authoritative result channel. Polling exists for progress indicators and recovery, not for driving business logic.

This page covers configuration and delivery semantics. For the payload itself — every field, every object — see The webhook payload.

  1. Portal → Connect → Add Webhook.

  2. Enter your HTTPS endpoint.

  3. For Authorization choose Bearer, and set a secret. UpPass sends it on every delivery as Authorization: Bearer {secret}.

  4. Subscribe to submit_form at minimum. Other events are listed under event.type.

One flow can have several receivers.

What UpPass sends, and what your endpoint must do.

Method POST
Content type application/json
Transport HTTPS with a valid certificate
Auth header Authorization: Bearer {your configured secret}
Expected response Any 2xx, within 30 seconds
Body Submission payload on submit_form; lifecycle envelope otherwise

Your endpoint must:

  • Reject a mismatched Authorization header. It is the only thing authenticating the caller.
  • Return 2xx within 30 seconds. Acknowledge first and process afterwards if your handling is slow. See the delivery rules below.
  • Deduplicate on application.slug + application.submitted_at. See below.
  • Fetch signed image URLs within 15 minutes. See below.
Your receiver… UpPass…
Returns 2xx within 30 seconds Marks the delivery successful
Returns 5xx Retries, up to three times
Returns 4xx Does not retry
Does not answer within 30 seconds Does not retry

A delivery that is not retried is not lost — recover it with Resend webhook. Answer 5xx only for a genuine transient failure on your side; answering 5xx for a payload you cannot process just replays it three times.

Three delivery semantics that affect your design

Section titled “Three delivery semantics that affect your design”

event.nounce is unique per delivery, not per application. A resend, or an automatic retry after your receiver answered 5xx, arrives with a different nonce and passes any nonce-only check.

Use application.slug + application.submitted_at as the idempotency key. Both are stable for the life of the application. Keep nounce for collapsing an immediate transport-level retry, if you want it at all.

Not the submission time. On a first delivery the two are seconds apart; on a re-delivery they can be far apart.

Read application.submitted_at for when the applicant finished.

If you miss the window it is recoverable — a resend mints fresh URLs.

extra.identity_aml is unbounded and dominates the payload. One sanctions match carried a dozen source lists with several hundred aliases; adverse-media matches carry dozens of article snippets.

Size-test your receiver against a sanctions-match payload before go-live. Body limits on reverse proxies and serverless gateways are a realistic failure mode.

Terminal window
curl -O https://docs.uppass.io/examples/webhook-submit_form-aml-hit.json

That file is trimmed at __TRIMMED__ markers, so it is smaller than a production payload. Treat it as a floor, not a ceiling.

Terminal window
curl --request POST \
'https://app.uppass.io/{lang}/api/forms/{form_slug}/applied-forms/{slug}/hook-submit/' \
--header 'Authorization: Bearer '"$UPPASS_API_TOKEN"

Returns 204.

The resent payload has the same shape, with a new nounce and a new event.created_at — which is why idempotency must not key on either.

Complete payloads are published as JSON:

Terminal window
curl -O https://docs.uppass.io/examples/webhook-submit_form-clean.json
curl -O https://docs.uppass.io/examples/webhook-submit_form-aml-hit.json
curl -O https://docs.uppass.io/examples/webhook-drop_off.json

POST them at your own endpoint with the Authorization header you configured.