POSTs to a URL configured for your tenant whenever a case decision is
reached (or later overridden), when a workflow node asks an external system to
act, or when a node emits a notification mid-run.
Your webhook URL, signing secret, secret ID, and retry policy are provisioned
for you by the Frayme team. These will move to self-service in the portal.
Events
event_type | When | Payload |
|---|---|---|
case.decided | Workflow finished with approved or declined. | Decision |
case.pending_review | Workflow finished with in_review; the case is now in a queue. | Decision |
case.decision_overridden | An analyst overrode an existing decision. | Decision |
external_callback.dispatch | A workflow node paused for an external system. Frayme is asking that system to do work and post back to a one-time callback URL. | Callback dispatch |
node.notification | A node configured to notify emitted output mid-run — for example, a Sumsub node publishing a hosted verification link and a pending-action handle before it pauses. | Node notification |
case.rfi_requested | A request for information was raised on a case (by an analyst, a workflow node, or an API call). Your systems send the resulting email — Frayme never does. | RFI requested |
event_type is always snake_case. Decision events and case.rfi_requested
also use snake_case for their top-level identity keys (case_id,
tenant_id); the external_callback.dispatch and node.notification events
use camelCase (caseId, tenantId). The examples below reflect the exact
keys on the wire.Payload shapes
Decision events
Sent forcase.decided, case.pending_review, and case.decision_overridden.
The body carries the full decision result so you can act without re-fetching
the case.
external_customer_idis present only when the case carried one — it is the stable key to correlate the decision back to your own user record.resultis the same envelope returned byGET /cases/{caseId}:result.decisionis the current decision,result.decisionHistory[0]is the workflow’s initial decision, and later entries are analyst overrides.result.decision.sourceis one ofworkflow,risk_evaluation, oranalyst.result.workflow_resultis write-once — analyst overrides never mutate it.
case.decision_overridden event, the new result.decision reflects the
analyst’s override and a new entry is appended to result.decisionHistory.
External callback dispatch
Sent forexternal_callback.dispatch. The receiving system performs its work,
then posts the result to the one-time callbackUrl (see
Delivering an external callback)
before expiresAt.
Node notification
Sent fornode.notification when a node configured to notify emits output.
payloadType identifies the output schema (for example,
sumsub-application-start); output carries the node’s emitted fields,
including a pending_handle when the node also exposes
pending actions.
payloadType and tag appear only when configured on the node. The output
keys depend on the node’s provider and configuration. A Sumsub node that allows
retries emits a node.notification with payloadType: sumsub-retry-requested
each time the applicant must resubmit — see
Data sources → Sumsub → Resubmission retries.
RFI requested
Sent forcase.rfi_requested when a request for information is raised on a case
— by an analyst in the review console, a workflow node, or your own API call.
Frayme never sends the email itself; your systems send it to the customer
using this payload. Delivered to your tenant’s default webhook configuration (or
the one named by webhook_key).
sourceis one ofanalyst,workflow, orapi— who raised the request.requested_byis a prefixed identifier:analyst:<userId>,node:<nodeId>, orapikey:<keyId>.template_id(when a template was used),cc, andwebhook_keyare present only when set.
Deduplicate this event on
rfi_id, not webhookId. Re-raising a
still-pending RFI legitimately re-publishes the event with a fresh
webhookId, so the webhookId rule below would let a duplicate through.Headers
Every outbound webhook carries these headers.| Header | Value |
|---|---|
X-Frayme-Signature | HMAC-SHA256 of the raw request body, keyed by your signing secret, hex-encoded. |
X-Frayme-Secret-ID | (When set) identifier of the active signing secret — used during secret rotation. |
Content-Type | application/json |
Verifying the signature
Compute the HMAC over the exact bytes of the request body — do not re-serialize the JSON — and compare it toX-Frayme-Signature in constant time.
- Compute HMAC-SHA256 over the raw body bytes; compare hex digests with a
constant-time comparison (for example,
hmac.compare_digest). - If
X-Frayme-Secret-IDis present, select the matching secret from your rotation set before computing the HMAC. - Respond
2xxwithin the per-delivery timeout (about 10 seconds).
Deduplication
Every outbound webhook carries a uniquewebhookId (UUID). Delivery is
at-least-once — a transient failure or network blip can produce duplicate
deliveries with the same webhookId. Persist the webhookId of every event you
successfully process, and short-circuit any event whose webhookId you have seen
before.
The one exception is case.rfi_requested: deduplicate it on
rfi_id, because re-raising a still-pending RFI re-publishes with a fresh
webhookId.
Retries
A non-2xx response or a timeout is retried with exponential backoff — each
wait is twice the last (1s, 2s, 4s, 8s, …) — up to a configured maximum number
of attempts (5 by default). After the final attempt the delivery is recorded as
failed and not retried further, so treat webhooks as best-effort and reconcile
with GET /cases/{caseId} if you must
be certain of a case’s current decision.