Events: the five moments your app calls
Five events cover the moments fraud happens: sign-up, login, a new phone or SIM, a new payee and the payment. Call each once, before the action takes effect, and act on the decision that comes back. Each call is read against everything bound and read earlier in the session.
Names and fields are illustrative and agreed in the sandbox before any contract.
On this page
Calling events
- Call before the action takes effect.
Before the account is created, the session opened, the new phone trusted, the payee saved or the payment sent. A decision after the fact can only open a case.
- Pass your own identifiers, never names or card numbers.
The SDK has read the session since the app opened: the network and SIM, the device and its integrity, the owner’s rhythm, an active call, a shared screen, the payee’s links. Your app passes only what identifies the customer and the payee in your systems.
- One call per event, and one id across three places.
Each call returns one decision. Its decision_id is the same in the app, in the event your FRM receives and in the decision log, so a complaint or an audit can follow one id end to end.
Sign-up signup.start
When a new customer starts opening an account, before it is created. The call runs the verification modules your journey names and returns one decision before the account can move money.
| Field | Type | What to pass |
|---|---|---|
| phone | string | The number the applicant typed. The network check starts as soon as it is known. |
| journey | string | Your name for the flow, such as retail or merchant, so the right policy applies. |
| checks | string[] | The verification modules this journey runs: ekyc, docs, liveness, vkyc, kyb, aml, bank. Leave it out to run what the policy names. |
The SDK reads Identity and eKYC, Document and ID verification, Liveness and face match, AML screening, Bank account verification, Silent network verification, SIM and device binding, Device intelligence and Payee and mule graph. In the story the answer is allow (allow): Ananya opens her account. The network confirms her number, the SIM and phone are bound, a live face matches Aadhaar, and screening is clear.
The call
signup.start.ts
const decision = await deepid.decide(
"signup.start",
{
phone: form.phone,
journey: "retail",
},
)
if (decision.action === "allow") openAccount()
else show(decision.message)What comes back
decision.json
{
"decision_id": "dec_7q2_002",
"event": "signup.start",
"session": "ses_8f2c91ab",
"action": "allow",
"reasons": [
{ "code": "NUMBER_VERIFIED", "module": "access" },
{ "code": "SIM_BOUND", "module": "binding" },
{ "code": "LIVE_PERSON", "module": "identity" },
{ "code": "FACE_MATCHED", "module": "identity" },
{ "code": "AML_CLEAR", "module": "aml" }
],
"message": {
"key": "signup_verified",
"text": "You’re verified. No code was needed."
},
"queue": null,
"policy": { "id": "retail", "version": 14 },
"created_at": "2026-08-12T09:14:02Z"
}The server’s verdict comes before the app’s: liveness gives an instant answer on the phone, and your server asks DeepID for the final verdict before the account opens. When in doubt it fails closed.
Login login.attempt
When a returning customer signs in, before you open the session. A device refused here gets no login token, whichever route would have verified the number.
| Field | Type | What to pass |
|---|---|---|
| customer | string | Your customer id, as your systems know it. Never a name, a number or a card. |
The SDK reads Silent network verification, SIM and device binding, Device intelligence and Behavioural biometrics. In the story the answer is allow (allow): Ananya opens the app on her own phone. The bound SIM and the known device do the work; no code is sent.
The call
login.attempt.ts
const decision = await deepid.decide(
"login.attempt",
{ customer: user.id },
)
if (decision.action === "allow") signIn()
else show(decision.message)What comes back
decision.json
{
"decision_id": "dec_7q2_091",
"event": "login.attempt",
"session": "ses_8f2c91ab",
"action": "allow",
"reasons": [
{ "code": "NUMBER_VERIFIED", "module": "access" },
{ "code": "SIM_BOUND", "module": "binding" },
{ "code": "KNOWN_DEVICE", "module": "device" }
],
"message": {
"key": "login_ok",
"text": "Welcome back. Nothing to type."
},
"queue": null,
"policy": { "id": "retail", "version": 14 },
"created_at": "2026-10-22T12:48:10Z"
}On allow, the routes run and the app receives a single-use token. Your server redeems it with DeepID, server to server, and only then signs the customer in. See the quickstart.
New phone or SIM device.change
When the SDK sees the SIM in a different phone, or the app on a new device, before the new phone can add a payee or pay.
| Field | Type | What to pass |
|---|---|---|
| customer | string | Your customer id, as your systems know it. Never a name, a number or a card. |
The SDK reads Silent network verification, SIM and device binding, Liveness and face match and Device intelligence. In the story the answer is step up (step_up): Three weeks earlier her SIM moved to a new phone. A face check against the face from sign-up, then the account is bound again.
The call
device.change.ts
const decision = await deepid.decide(
"device.change",
{ customer: user.id },
)
if (decision.action === "allow") bindDevice()
else show(decision.message)What comes back
decision.json
{
"decision_id": "dec_7q2_044",
"event": "device.change",
"session": "ses_8f2c91ab",
"action": "step_up",
"reasons": [
{ "code": "SIM_MOVED", "module": "binding" },
{ "code": "NEW_DEVICE", "module": "device" }
],
"message": {
"key": "face_check",
"text": "Confirm it’s you with a quick face check."
},
"queue": null,
"policy": { "id": "retail", "version": 14 },
"created_at": "2026-09-30T18:02:41Z"
}New payee payee.add
When the customer confirms a payee’s details, before the payee is saved. The moment scams and mules need, read for the payee and for pressure on the customer.
| Field | Type | What to pass |
|---|---|---|
| customer | string | Your customer id, as your systems know it. Never a name, a number or a card. |
| payee | string | The payee as your systems know it: a VPA, an account with its IFSC, or a wallet id. The graph scores it; the name is not needed. |
The SDK reads Device intelligence, Behavioural biometrics, Scam signals and Payee and mule graph. In the story the answer is cool off (cool_off): On a call, Ananya adds a payee read out to her: an account 2 days old, linked to 14 others. She can send ₹10,000 to it for 12 hours.
The call
payee.add.ts
const decision = await deepid.decide(
"payee.add",
{
customer: user.id,
payee: payee.vpa,
},
)
if (decision.action === "allow") savePayee()
else show(decision.message)What comes back
decision.json
{
"decision_id": "dec_7q2_117",
"event": "payee.add",
"session": "ses_8f2c91ab",
"action": "cool_off",
"reasons": [
{ "code": "FIRST_TIME_PAYEE", "module": "graph" },
{ "code": "PAYEE_ACCOUNT_NEW", "module": "graph" },
{ "code": "PAYEE_MULE_LINKED", "module": "graph" },
{ "code": "ACTIVE_CALL", "module": "scam" }
],
"message": {
"key": "new_payee_limit",
"text": "You can send up to ₹10,000 to this payee now. You get the full limit in 12 hours."
},
"queue": null,
"policy": { "id": "retail", "version": 14 },
"created_at": "2026-10-22T12:50:31Z"
}Credits into an account are read from your core system’s events, not from a call in the app. The call here is for the payee the customer is adding.
Payment payment.initiate
When the customer confirms a payment, before your app sends the instruction to the rail. Read against everything earlier in the session.
| Field | Type | What to pass |
|---|---|---|
| customer | string | Your customer id, as your systems know it. Never a name, a number or a card. |
| payee | string | The payee as your systems know it: a VPA, an account with its IFSC, or a wallet id. The graph scores it; the name is not needed. |
| amount | object | value and currency. In minor or major units as your sandbox specifies; the example uses rupees. |
| rail | string | UPI, IMPS, NEFT or your own name for it, so limits and cool-offs apply per rail. |
The SDK reads Device intelligence, Behavioural biometrics, Scam signals and Payee and mule graph. In the story the answer is hold (hold): Three minutes later, still on the call and sharing her screen, she starts a payment of ₹96,000. It is held, and the bank calls her back.
The call
payment.initiate.ts
const decision = await deepid.decide(
"payment.initiate",
{
customer: user.id,
payee: payee.vpa,
amount: { value: 96000, currency: "INR" },
rail: "UPI",
},
)
if (decision.action === "allow") sendPayment()
else show(decision.message)What comes back
decision.json
{
"decision_id": "dec_7q2_118",
"event": "payment.initiate",
"session": "ses_8f2c91ab",
"action": "hold",
"reasons": [
{ "code": "ACTIVE_CALL", "module": "scam" },
{ "code": "SCREEN_SHARED", "module": "device" },
{ "code": "DICTATION_PAUSES", "module": "behaviour" },
{ "code": "PAYEE_MULE_LINKED", "module": "graph" }
],
"message": {
"key": "payment_paused_callback",
"text": "Payment paused. Your money hasn’t left your account. We’ll call you on +91 •••• 3210."
},
"queue": "scam-callback",
"policy": { "id": "retail", "version": 14 },
"created_at": "2026-10-22T12:53:34Z"
}Every decision’s shape, field by field, is on the decision object page; the codes it can carry are in the reason-code catalogue.
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.