Skip to content

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

  1. 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.

  2. 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.

  3. 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.

What your app passes at signup.start
FieldTypeWhat to pass
phonestringThe number the applicant typed. The network check starts as soon as it is known.
journeystringYour name for the flow, such as retail or merchant, so the right policy applies.
checksstring[]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.

What your app passes at login.attempt
FieldTypeWhat to pass
customerstringYour 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.

What your app passes at device.change
FieldTypeWhat to pass
customerstringYour 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.

What your app passes at payee.add
FieldTypeWhat to pass
customerstringYour customer id, as your systems know it. Never a name, a number or a card.
payeestringThe 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.

What your app passes at payment.initiate
FieldTypeWhat to pass
customerstringYour customer id, as your systems know it. Never a name, a number or a card.
payeestringThe 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.
amountobjectvalue and currency. In minor or major units as your sandbox specifies; the example uses rupees.
railstringUPI, 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.