PushMirror mobile guide
Step-by-step for beginners, intermediate, and advanced developers. Keys, Firebase, device registration, SSE (not WebSocket), webhooks, and every challenge model with a live phone demo.
Three secrets. Three jobs.
Most confusion comes from mixing these. Memorize the table, then pick your level.
Project API key
ZINDUA_API_KEY = znd_live_…Where: Dashboard → Project → API keys
Used for: Your backend creates challenges with Authorization: Bearer …
Never put this key inside the mobile APK / IPA or a public frontend.
Webhook secret
ZINDUA_WEBHOOK_SECRET = znd_sec_…Where: Dashboard → Project → Push → Config → Webhook secret (Rotate)
Used for: Verify X-Zindua-Signature on challenge.approved / denied / expired events.
Rotate anytime. Old secret stops working immediately.
Firebase FCM server key
(stored in Zindua only) = AAAA…Where: Firebase Console → Project settings → Cloud Messaging → Server key → paste in Push → Config
Used for: Lets Zindua (or your backend) deliver the push into your Android / iOS app.
Not the same as znd_live_. Do not put FCM server key in the app binary either.
SSE for the login page. Webhooks for your server.
People often say WebSocket. PushMirror uses Server-Sent Events for the browser, and signed HTTP webhooks for backends.
SSE (recommended for login screens)
The browser opens EventSource on GET /api/v1/challenges/:id/stream. When the phone responds, the login page unlocks in milliseconds. This is not a WebSocket — it is Server-Sent Events (one-way stream).
Webhooks (recommended for backends)
Zindua POSTs challenge.approved / denied / expired to your URL. Verify HMAC with ZINDUA_WEBHOOK_SECRET. Best for servers, workers, and audit logs.
Polling (fallback only)
GET /api/v1/challenges/:id every few seconds if SSE is blocked. Prefer SSE or webhooks in production.
PushMirror. Switch models. Watch the push.
PushMirror mark, Powered by Zindua, check + celebration burst on approve — same sheet as production.
New machine sign-in
Check your phone
Tap the same icon on your trusted device.
Your app
PushMirror · now
Allow this sign-in?
Chrome · Kinshasa · just now
Emoji, digit, or approve
Pick one type per login. The interactive demo above switches the same UI.
Emoji match
Login: Login shows one emoji (😄)
Phone: Phone shows 3–5 emojis — user taps the same one
Harder to phish by reading a code aloud
Digit match
Login: Login shows a two-digit number (42)
Phone: Phone shows several numbers — user picks 42
Same security idea as digit match in PushMirror
Approve / Deny
Login: Login waits for a Yes / No on the phone
Phone: It's me · Not me
Fastest UX for low-risk confirmations
Follow only the track you need
Start beginner even if you ship mobile apps. Intermediate adds Firebase. Advanced hardens production.
Dashboard + playground, no mobile code yet
Create a Zindua project
Sign in → create a project → copy znd_live_… (or znd_test_…). Same key as email and WhatsApp.
Open PushMirror in the dashboard
Project → Push. Set App name. Leave Logo empty to use the PushMirror default icon. Pick default challenge: emoji, digit, or approve.
Try the playground first
Use Playground with a phone number on your linked WhatsApp line. You will see the desktop symbol and phone choices without writing mobile code yet.
Understand the three challenge types
Approve = Yes / No. Emoji = tap the same icon as the login screen. Digit = pick the same number. Switch them in the interactive demo below.
Copy in order
Server creates → browser listens (SSE) → phone registers device → phone responds → optional webhook.
import { Zindua } from "@zindua/sdk";
const zindua = new Zindua({ apiKey: process.env.ZINDUA_API_KEY! });
// Called when someone signs in from a NEW machine
const challenge = await zindua.pushMirror.create({
userId: "usr_123",
type: "emoji", // "emoji" | "approve" | "digit"
purpose: "login",
context: {
device: "Chrome on Windows",
city: "Kinshasa",
},
fallback: { channel: "whatsapp", to: "+243832499559" },
});
// Login page shows challenge.display (😄 or 42)
// Branding comes from Push settings (app name + logo or PushMirror default)
return {
challengeId: challenge.id,
display: challenge.display,
branding: challenge.branding, // if your SDK surfaces it
};Also on Developers
Short Confirm section with key placement + downloadable example. This mobile page is the deep guide.
Ready to wire your mobile app?
Create a project, paste your FCM key under Push → Config, register a device token, then create your first challenge.