Webhooks: one decision, three places
The app gets the decision first, so the customer sees the right next step at once. The same decision then reaches your FRM, case manager or SIEM as an event, with the same reasons, and the decision log keeps it. Your FRM stays in charge; DeepID feeds it.
Names and fields are illustrative and agreed in the sandbox before any contract.
On this page
One decision, three places
One coached payment, as it reached each place. The times are the story’s: a 41-minute call, a payee added 3 minutes before the payment.
- 12:12A video call begins.
A man in uniform tells Ananya a crime is linked to her identity and that she must stay on camera.
Her phone - 12:48She opens the app.
login.attemptreturns allow.Her own phone, the bound SIM, no code sent. The session the SDK reads from here carries the call that is already 36 minutes old.
Her app and DeepID - 12:50She shares her screen and adds the payee.
payee.addreturnscool_off.An account 2 days old, linked to 14 others. The app tells her she can send up to ₹10,000 to it for 12 hours.
Her app and DeepID - 12:53:34She starts a payment of ₹96,000.
payment.initiatereturns hold.The app shows the message and sends nothing to the rail: “Payment paused. Your money hasn’t left your account. We’ll call you on +91 •••• 3210.”
Her app - 12:53:34
decision.createdreaches your FRM.The same decision id, the same four reasons (ACTIVE_CALL, SCREEN_SHARED, DICTATION_PAUSES, PAYEE_MULE_LINKED) and the queue scam-callback. Your case manager opens the case under the id.
Your FRM and case manager - 12:53:34The decision log keeps it.
The reasons, the message as shown, the policy (retail, version 14) and the time, for the analyst and the audit.
The decision log - AfterYour team calls Ananya on +91 •••• 3210, her registered number.
The money never left. The decision, its reasons and the call-back are one record, under one id.
Your call-back team
The event
The event carries the decision, not a summary of it. Your FRM sees the same reasons and the same queue the app saw, under the same id.
To your FRM
POST /hooks/deepid
POST /hooks/deepid HTTP/1.1
Content-Type: application/json
{
"type": "decision.created",
"data": {
"decision_id": "dec_7q2_118",
"event": "payment.initiate",
"session": "ses_8f2c91ab",
"customer": "cus_4821",
"action": "hold",
"reasons": [
"ACTIVE_CALL",
"SCREEN_SHARED",
"DICTATION_PAUSES",
"PAYEE_MULE_LINKED"
],
"message": {
"key": "payment_paused_callback",
"shown": true
},
"queue": "scam-callback",
"policy": { "id": "retail", "version": 14 },
"created_at": "2026-10-22T12:53:34Z"
}
}| Field | Carries |
|---|---|
| type | decision.created, for every decision. Other event types are agreed in the sandbox. |
| data.decision_id | The decision’s id, the same one the app received. The key for everything downstream. |
| data.event | The moment the app called at. |
| data.session | The app session the decision was read against. |
| data.customer | Your customer id, as the app passed it. Never a name. |
| data.action | The action the app was told to take. |
| data.reasons | The reason codes, risk first. |
| data.message | Which customer message was shown, by key, and that it was shown. |
| data.queue | The queue a person should work, or null. |
| data.policy | The policy and version that made the decision. |
| data.created_at | When the decision was made, in UTC. |
Where it goes
- Your FRM
Keeps its own rules, queues and case management. DeepID adds the signals it never receives today: the call, the shared screen, the dictation pauses, the payee’s links. Nothing is replaced.
- Your case manager
Opens a case when the decision names a queue, such as a call back, under the decision id, so the call-back team reads the same reasons the customer’s message was written from.
- Your SIEM
Receives the device and session signals for the security team: emulators, tampered devices, remote access and screen sharing, beside your other events.
- The decision log
Keeps every decision with its reasons, the message shown and the policy version, for audit.
Handling events
Act on the event in your backend rather than on what the app reports. Treat data.decision_id as the key: a repeated delivery is stored once and opens no second case. The handler below does both.
Your backend
hooks.ts
// Act on what DeepID sent, not on what the app
// reports. The decision id is the key: a repeated
// delivery is stored once and opens no second case.
app.post("/hooks/deepid", async (req, res) => {
const { type, data } = req.body
if (type !== "decision.created") {
return res.sendStatus(200)
}
const fresh = await decisions.put(
data.decision_id,
data,
)
if (fresh && data.queue) {
await cases.open(data.queue, data)
}
res.sendStatus(200)
})When a service needs the decision before the event arrives, it can ask for it by id, server to server.
Your backend
Request
GET /v1/decisions/dec_7q2_118
Authorization: Bearer <server-key>How events are delivered, signed and retried, and the endpoints and headers your gateway will see, are agreed in the sandbox with your platform team.
The decision log
The decision log is the audit record: every decision, its reasons, the message as the customer read it and the policy version that applied. A complaint, a regulator’s question or an internal review starts from one id and reads one record. How long the log and the signed face-check records are kept is settled in writing with your security team before a pilot, as the security page sets out.
Wire one moment in the sandbox, before any contract.
Sign-up or login end to end, with the decision back in the app and as an event, so your engineers see the shape of it before anyone signs anything.