Quickstart: a first decision in six steps
Install the SDK, start it when the app opens, call it when the customer confirms a payment and branch on the action. Then receive the same decision in your backend. The examples use the web SDK; Android, iOS, React Native and Flutter follow the same three steps.
Names and fields are illustrative and agreed in the sandbox before any contract.
On this page
1. Request sandbox access
Sandbox access comes before any contract. Request it and name the journey you want to protect first. Access for your platforms, the event names and fields you will build against, and the questions your security team will want settled before a pilot arrive with it.
2. Install the SDK
One package per platform. The web SDK and the Android SDK are shown; the SDKs page has iOS, React Native and Flutter.
Install
Terminal
npm install @deepid/webInstall
build.gradle.kts · Android
dependencies {
implementation(
"com.deepidsdk:deepid-android:<version>"
)
}3. Start it once
Start the SDK as the app opens, so it has read the session before the first call. From then on it reads the network and SIM, the device and its integrity, the owner’s rhythm, an active call and a shared screen. Your app passes none of this.
Start once
app.ts
import { createDeepID } from "@deepid/web"
// Once, as the page loads
const deepid = createDeepID({ key: DEEPID_SANDBOX_KEY })4. Call at the payment
Call decide when the customer confirms the payment, before you send it to the rail. Pass your own identifiers and the payment’s details; the SDK adds what it has read. Ananya’s payment: ₹96,000 over UPI to a payee added 3 minutes ago.
The call
payment.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)5. Handle all six actions
Six actions, in ladder order. Show decision.message.text as it is, or map decision.message.key to your own copy and languages. Handle every action explicitly, so a new policy rule never falls through to a default you did not intend.
| Action | When | The customer sees, for example |
|---|---|---|
| allow | Everything holds. | Payment sent. |
| warn | Something is unusual, but the customer is in control. | This payee is new to you. Check the name before you send. |
| step_up | Identity is uncertain. | Confirm it’s you with a quick face check. |
| cool_off | A new device, SIM or payee. | You can send up to the starter limit now. You get the full limit in 12 hours. |
| hold | Signs of coercion or a mule payee. | Payment paused. Your money hasn’t left your account. We’ll call you on +91 •••• 3210. |
| block | Clear attack. | We couldn’t verify this session. Your money is safe. |
PaymentViewModel.kt
// The SDK has read the session since launch.
val decision = DeepID.decide(
Event.PaymentInitiate(
customer = user.id,
payee = payee.vpa,
amount = Money(96_000, "INR"),
rail = "UPI",
)
)
when (decision.action) {
Action.ALLOW -> sendPayment()
Action.WARN -> showWarning(decision.message)
Action.STEP_UP -> stepUp(decision.message)
Action.COOL_OFF -> applyLimit(decision.message)
Action.HOLD -> showPaused(decision.message)
Action.BLOCK -> endSession(decision.message)
}PaymentModel.swift
// The SDK has read the session since launch.
let decision = try await DeepID.decide(
.paymentInitiate(
customer: user.id,
payee: payee.vpa,
amount: Money(96_000, "INR"),
rail: "UPI"
)
)
switch decision.action {
case .allow: sendPayment()
case .warn: showWarning(decision.message)
case .stepUp: stepUp(decision.message)
case .coolOff: applyLimit(decision.message)
case .hold: showPaused(decision.message)
case .block: endSession(decision.message)
}payment.ts
// The SDK has read the session since launch.
const decision = await deepid.decide(
"payment.initiate",
{
customer: user.id,
payee: payee.vpa,
amount: { value: 96000, currency: "INR" },
rail: "UPI",
},
)
switch (decision.action) {
case "allow": return sendPayment()
case "warn": return showWarning(decision.message)
case "step_up": return stepUp(decision.message)
case "cool_off": return applyLimit(decision.message)
case "hold": return showPaused(decision.message)
case "block": return endSession(decision.message)
}6. Receive the event
The same decision reaches your backend as an event, so a payment service acts on what DeepID sent rather than on what the app reports. The decision id is the key: a repeated delivery is stored once. See Webhooks.
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)
})That is the first moment, end to end. Try one-tap login on your own number to see the login moment before you wire it.
Integrating each moment
Start with the moment your first journey covers. Each later moment is one more call from the same SDK, read against everything bound and read before it, and its reasons join the same decision shape.
Sign-up signup.start
The real person, bound to their own phone and SIM.
- 1
Start the SDK when sign-up opens
The session begins with the first screen, so the device and its integrity are read from the start.
- 2
Verify the identity
Aadhaar through DigiLocker or offline KYC, PAN and other ids, checked at the source, with documents or video KYC where your journey needs them.
- 3
Take the selfie
Liveness gives the customer an instant answer on the phone, and catches photos, masks, deepfakes and injected streams.
- 4
Confirm on your server
Your server asks DeepID for the final verdict before the account opens. When in doubt it fails closed: deny or review, never pass.
- 5
Bind the phone and SIM
The account is bound to this device and its SIM, so every later moment has something to check against.
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)Returns: Allow, step up or block, with its reasons, to the app and as an event to your FRM. Reads with 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.
Login login.attempt
One tap, and the SIM bound at sign-up is checked.
- 1
Ask for the number
The network check starts as soon as the number is typed, so it is often done before the customer taps Continue.
- 2
Let the routes run in order
The network check, the SIM check, Aadhaar consent, then a one-time code. The first that verifies ends it, or your policy requires all of them.
- 3
Pass the token to your server
The app receives a single-use token. A device refused by the risk gate gets none.
- 4
Redeem it before signing in
Your server redeems the token with DeepID, server to server, and only then signs the customer in.
- 5
Expect the bound phone and SIM
The phone and SIM bound at sign-up are checked here. A change hands over to the new phone or SIM moment.
In the app
login.ts
// In the app: the routes run in your policy's order
// (network, SIM, Aadhaar consent, then a one-time code).
// The first that verifies ends it.
const { token } = await deepid.login({
phone: input.number,
})
await api.post("/session", { token })On your server
session.ts
// On your server: redeem the single-use token with
// DeepID, server to server, before signing anyone in.
const result = await deepidServer.redeem(token)
if (result.verified) return signIn(result.customer)
return reject()Returns: Allow, or a step-up when the binding or the device does not hold, with its reasons. Reads with Silent network verification, SIM and device binding, Device intelligence and Behavioural biometrics.
New phone or SIM device.change
The face from sign-up, checked before anything is bound again.
- 1
Notice the change
The SDK sees a new device, or a SIM that moved to another phone, when the app opens or at login.
- 2
Call before money can move
Call DeepID before the new phone can add a payee or pay.
- 3
Step up with a face check
The live face is checked against the one matched at sign-up.
- 4
Re-bind the customer
The account is bound to the new phone and SIM only after the customer proves it is them.
- 5
Cool off
What can leave the account is limited for as long as your policy and market require: ₹10,000 for 12 hours in the story.
The call
device.change.ts
const decision = await deepid.decide(
"device.change",
{ customer: user.id },
)
if (decision.action === "allow") bindDevice()
else show(decision.message)Returns: Usually a step-up, then a cool-off, with its reasons. Reads with Silent network verification, SIM and device binding, Liveness and face match and Device intelligence.
New payee payee.add
The payee scored, from the phone and SIM already bound.
- 1
Call before the payee is saved
When the customer confirms the payee’s details.
- 2
DeepID reads the session
An active call or a shared screen, details pasted or dictated, the device, and the payee’s links in the mule graph.
- 3
Act on the decision
Allow, warn, cool off or hold, and show the message written for the customer.
- 4
Your FRM gets the same decision
As an event with its reasons, so your rules can use it.
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)Returns: Allow, warn, cool off or hold, with its reasons. Reads with Device intelligence, Behavioural biometrics, Scam signals and Payee and mule graph.
Payment payment.initiate
The SIM bound at login is the SIM expected at payment.
- 1
Call when the customer confirms
Before your app sends the payment instruction to the rail.
- 2
DeepID reads the session around it
From login to this screen: the call, the shared screen, the typing and the payee, against everything read before.
- 3
Act on the action
Show the message it carries. A held payment comes with a call back on the registered number, +91 •••• 3210 in the story.
- 4
The same decision reaches your FRM
With its reason codes, logged for audit.
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)Returns: One of six actions, from allow to block, with its reasons. Reads with Device intelligence, Behavioural biometrics, Scam signals and Payee and mule graph.
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.