Set up Approve
Let users approve a sign-in or a payment on a phone they already use: enrol the phone after login, ask it from your server, and show the number to match.
Approve sends a request to a phone where the user is already signed in to your app. The phone shows what is happening (“Sign in from Chrome on Windows, Pune”) and three numbers. The user picks the number shown on your login page and confirms with a fingerprint, face or screen lock. Your server gets approved in a few seconds.
Use it for sign-ins on a new computer, payments, payouts, password and email changes, and anything else you would confirm with an SMS code today.
What you need first
- An Android or iOS app where users sign in. Approve works only on a phone that has signed in before; sign-up and a lost phone need another way in (email OTP, a passkey, or authenticator backup codes).
- Push set up for that app, because the request reaches the phone as a push: Android (Firebase service account) and iOS (
.p8key or Firebase). - The SignOps phone SDK: Android (
com.signops:android), iOS (SignOpson Swift Package Manager), React Native (@signops/react-native) or Flutter (signops). They are not on Maven Central, Swift Package Manager, npm or pub.dev yet. The REST calls they make are in the API reference, so you can see exactly what they do. - Your publishable key (
pk_live_...), from Approve, Setup or API keys. It can only enrol a phone and answer approvals, so it is safe inside the app.
1. Trust the phone after your own login
Once the user has signed in to your app on their phone, your server makes a one-time enrol token for them, and the app enrols:
// Your server, for the user who just signed in
const { token } = await signops.approve.enrollToken({ user_id: "usr_8f21c4" });
// send token to the app
// Android
val signops = SignOps(context, "pk_live_...")
signops.approve.enroll(token, FirebaseMessaging.getInstance().token.await())
// iOS
let signops = SignOps(key: "pk_live_...")
try await signops.approve.enroll(token: token, pushToken: SignOps.apnsToken(from: deviceToken)) // or the FCM token
The SDK makes a P-256 key pair in the Keystore or Secure Enclave, sends only the public half, and adds the push token as one of the user’s devices. Your dashboard’s Users page now shows the phone as trusted, and your webhook gets device.trusted.
On iOS, add NSFaceIDUsageDescription to Info.plist. On Android, the activity that shows the prompt must be a FragmentActivity (AppCompatActivity is one).
2. Show the request when the push arrives
// Android: in your FirebaseMessagingService
override fun onMessageReceived(m: RemoteMessage) {
Approve.requestId(m.data)?.let { signops.approve.open(this, it); return } // the ready-made screen
// ... your other pushes
}
override fun onNewToken(t: String) { scope.launch { signops.approve.updatePushToken(t) } }
// iOS (SwiftUI): the ready-made screen
.sheet(item: $approveId) { id in ApproveView(signops: signops, requestId: id.value) { _ in approveId = nil } }
Some phones hold pushes while the app sleeps, so also check when the app opens: if (signops.approve.isEnrolled && signops.approve.pending().isNotEmpty()) signops.approve.open(this). To use your own screen instead, read signops.approve.pending() and call approve(req, picked) or deny(req).
3. Ask the phone from your server
When the user signs in on the website (or starts a payment), create a request and show its match number on that page:
const req = await signops.approve.create({
user_id: "usr_8f21c4",
context: "Sign in from Chrome on Windows", // shown on the phone
city: "Pune",
});
// Show req.match ("47") on the login page: "Tap 47 on your phone"
const result = await signops.approve.wait(req.id, { timeout: 120 });
if (result.status === "approved") startSession(result.user_id);
// "denied": the user said it was not them. "expired": nobody answered in 2 minutes.
wait holds the connection for up to 120 seconds. If your server cannot hold a request that long, take the approve.approved, approve.denied and approve.expired webhooks instead, or poll GET /v1/approve/:id.
For a payment, put the amount and the payee in context, such as "Pay Rs 4,999 to Kirana Express", so the user approves exactly that.
4. Test it on your own phone
- Install your app on your phone with your test publishable key, sign in, and let it enrol (step 1).
- In the dashboard, open Approve, Setup, Try it on a trusted phone, type your user id and choose Send a test approval.
- The phone shows the request. Pick the number the dashboard shows and confirm with your fingerprint or face. The dashboard changes to Approved.
5. Plan the ways back in
Approve proves the user has their phone. When they do not:
- New phone, old one gone: sign in with an email code or a passkey, then enrol the new phone. The old one can be untrusted from the Users page or with
signops.approve.untrust(deviceId). - No trusted phone yet:
createanswers 404; offer email OTP or the authenticator. - Turn Approve off for the whole app under Approve, Settings. Requests then answer 403
not_allowed, so your login page should fall back.
To try several methods in a fixed order in one session (passkey, then Approve, then authenticator, then email), see Verify in one call on Starter and Pro.