Zindua

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.

Keys (read this first)

Three secrets. Three jobs.

Most confusion comes from mixing these. Memorize the table, then pick your level.

Beginner

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.

Intermediate

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.

Intermediate

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.

Realtime

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.

Interactive schematic

PushMirror. Switch models. Watch the push.

PushMirror mark, Powered by Zindua, check + celebration burst on approve — same sheet as production.

app.yours.com/login

New machine sign-in

Check your phone

😄

Tap the same icon on your trusted device.

Trusted phone

Your app

PushMirror · now

Allow this sign-in?

Chrome · Kinshasa · just now

Powered by Zindua
Challenge models

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

Choose your level

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

1

Create a Zindua project

Sign in → create a project → copy znd_live_… (or znd_test_…). Same key as email and WhatsApp.

2

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.

3

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.

4

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.

Code path

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.