Integrate Zoza Auth
Swap SMS OTP for a 2-second Curve25519 handshake. Register an app, enrol devices, issue challenges, verify cryptographic proofs — REST API live today, mobile SDKs land next.
Quickstart
Five HTTP calls end-to-end. You own steps 1 + 3 + 6; the user's phone owns steps 2 + 4 + 5.
# 1. You (bank) register your app — one time, returns your API key
$ curl -X POST https://auth-api.zoza.world/v1/apps \
-H "Content-Type: application/json" \
-d '{"name":"HDFC NetBanking","webhook_url":"https://hdfc.example.com/auth/wh"}'
{"id":"app_...", "api_key":"za_...", "webhook_key":"whk_...", "public_key":"<hex>"}
# 2. Each user enrols a device (you forward their device pubkey)
$ curl -X POST https://auth-api.zoza.world/v1/devices \
-H "Authorization: Bearer za_..." \
-d '{"user_id":"user_rahul","device_id":"iphone_15_pro","device_name":"iPhone 15 Pro","public_key":"<32-byte hex>"}'
# 3. You issue a challenge when the user logs in / approves a payment
$ curl -X POST https://auth-api.zoza.world/v1/challenges \
-H "Authorization: Bearer za_..." \
-d '{"user_id":"user_rahul","context":"payment","metadata":"Pay ₹5000 to Flipkart","ttl":30}'
{"challenge_id":"ch_...", "expires_at":"2026-04-17T14:22:48Z", "status":"pending"}
# 4. Device fetches challenge details (no auth — challenge_id is the secret)
$ curl https://auth-api.zoza.world/v1/challenges/ch_.../details
# 5. Device signs + POSTs the proof (client-side computation, see below)
$ curl -X POST https://auth-api.zoza.world/v1/challenges/ch_.../respond \
-d '{"device_id":"iphone_15_pro","client_pub":"<hex>","proof":"<hex>","approved":true}'
{"status":"approved", "user_id":"user_rahul", "verified_at":"2026-04-17T14:22:51Z"}
# 6. You poll /v1/challenges/ch_... (or receive the webhook)
$ curl https://auth-api.zoza.world/v1/challenges/ch_... \
-H "Authorization: Bearer za_..."
{"status":"approved", "used_at":"..."}
That's the whole loop. No SMS. No shared secret on the wire. Private key lives in the Secure Enclave / Android Keystore — our server never sees it.
Pick your integration
Zoza Auth ships one live surface today plus two planned SDKs. Use whichever fits your stack.
REST API (live)
HTTP + JSON. Call from Go, Java, Python, Kotlin, Swift. Full reference below. Every endpoint CORS-open.
Webhooks
Get pushed approved / denied results within 2s of the user's tap. HMAC-signed with your webhook_key.
iOS SDK
Swift wrapper over Secure Enclave key generation + biometric prompt. ETA 30 dev days post-launch.
Android SDK
Kotlin wrapper over Android Keystore + biometric prompt. ETA 25 dev days post-launch.
Your mobile team can use the REST API directly. Curve25519 is available in every platform: CryptoKit.Curve25519 on iOS 14+, java.security.KeyPairGenerator with "XDH" on Android 12+, libsodium everywhere else. Client-side crypto reference ↓ below.
Apply for an API Key
Submit below — we review within 24 hours and email your za_... API key on approval. Keys are shown once at issuance; store yours in a secret manager the moment you receive it.
Authentication
Pass your issued API key as a Bearer token on every customer-facing endpoint:
Authorization: Bearer za_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Device-facing endpoints (challenge fetch + respond) are not bearer-authenticated — the challenge_id is the single-use secret, valid for 30 seconds. This is intentional: the user's device should not have to ship your API key.
Base URL
https://auth-api.zoza.world
Also reachable at https://zoza-products.fly.dev/auth/… (with /auth prefix). The dedicated subdomain is the stable public surface.
REST API reference
Register app
api_key, a persistent webhook_key, and your server-side public_key. Save all three — keys are never shown again.Body: { "name": string, "webhook_url": string? }. Typical response fields:
| field | example | notes |
|---|---|---|
| id | app_8e41b805128845c2c1a4a9a047380d8f… | Identifies your app in challenge metadata + audit log. |
| api_key | za_f31a8bc9… | Authorise requests. Secret; rotate by calling POST /v1/apps again. |
| webhook_key | whk_cf22… | HMAC-key we use to sign outbound webhooks to your server. |
| public_key | <hex 32B> | Pin on the device at enrol time. Mutual-auth binds challenges. |
| rate_limit | 10 | Max challenges per user per minute. Adjustable at approve time. |
Register device
POST /v1/devices
Authorization: Bearer za_...
Content-Type: application/json
{
"user_id": "user_rahul", # your user identifier
"device_id": "iphone_15_pro", # unique per (user, device)
"device_name":"iPhone 15 Pro", # human-readable
"public_key": "<64 hex chars = 32 bytes>"
}
Revoke device
Issue challenge
POST /v1/challenges
Authorization: Bearer za_...
{
"user_id": "user_rahul",
"context": "payment", # free-form: login, payment, transfer, enrol
"metadata": "Pay ₹5000 to Flipkart", # shown to user in the biometric prompt
"ttl": 30 # seconds, 1-300. Default 30.
}
Response: { "challenge_id":"ch_...", "status":"pending", "expires_at":"...", "ttl_seconds":30 }
Poll challenge status
Fetch challenge (device-side)
challenge_id itself is the single-use secret. Expires after 30s or on use.GET /v1/challenges/ch_.../details
{
"challenge_id": "ch_...",
"context": "payment",
"metadata": "Pay ₹5000 to Flipkart",
"server_pub": "<hex 32B>",
"nonce": "<hex 32B>",
"expires_at": "2026-04-17T14:22:48Z"
}
Respond (device-side)
POST /v1/challenges/ch_.../respond
Content-Type: application/json
{
"device_id": "iphone_15_pro",
"client_pub":"<hex 32B>",
"proof": "<hex 32B>", # HKDF(DH(device_priv, server_pub), nonce)
"approved": true
}
→ 200
{"status":"approved", "user_id":"user_rahul", "device_id":"iphone_15_pro", "verified_at":"..."}
Audit log
device_registered, device_revoked, challenge_issued, challenge_approved, challenge_denied, challenge_expired. Retained 90 days. Full policy at /about/auth-audit.Client-side crypto reference
Until our native SDKs ship, your mobile team computes the proof directly with platform crypto. The protocol is: generate a Curve25519 keypair at enrol, retrieve the server's server_pub from /details, compute X25519 shared secret, HKDF-SHA256 with the nonce as salt. 20 lines per platform.
import CryptoKit
// Enrol — generate + store in Secure Enclave
let devicePriv = try SecureEnclave.P256.KeyAgreement.PrivateKey()
let devicePubHex = devicePriv.publicKey.rawRepresentation.map { String(format: "%02x", $0) }.joined()
// POST devicePubHex to /v1/devices via your server
// Respond — compute proof after biometric auth
func computeProof(serverPub: [UInt8], nonce: [UInt8]) throws -> Data {
let server = try P256.KeyAgreement.PublicKey(rawRepresentation: Data(serverPub))
let shared = try devicePriv.sharedSecretFromKeyAgreement(with: server)
let proof = shared.hkdfDerivedSymmetricKey(
using: SHA256.self,
salt: Data(nonce),
sharedInfo: "ZozaAuth_Proof_v1".data(using: .utf8)!,
outputByteCount: 32)
return proof.withUnsafeBytes { Data($0) }
}
import java.security.KeyPairGenerator
import javax.crypto.KeyAgreement
import javax.crypto.spec.SecretKeySpec
import javax.crypto.Mac
// Enrol — Android Keystore-backed XDH (X25519) keypair
val kpg = KeyPairGenerator.getInstance("XDH", "AndroidKeyStore")
kpg.initialize(KeyGenParameterSpec.Builder("zoza_auth_device", PURPOSE_AGREE_KEY)
.setAlgorithmParameterSpec(ECGenParameterSpec("X25519"))
.setUserAuthenticationRequired(true) // biometric gate
.build())
val kp = kpg.generateKeyPair()
// Respond — X25519 + HKDF-SHA256 with nonce as salt
val ka = KeyAgreement.getInstance("XDH", "AndroidKeyStore")
ka.init(kp.private)
ka.doPhase(serverPubKey, true)
val shared: ByteArray = ka.generateSecret()
val proof = HkdfSha256.derive(shared, nonce, "ZozaAuth_Proof_v1".toByteArray(), 32)
Reference implementation in Go lives at products/zoza-auth/auth.go:537 — computeAuthProof. If your platform crypto doesn't agree with our Go output bit-for-bit, email us with your test vectors and we'll find the disagreement.
Webhooks
Instead of polling GET /v1/challenges/{id}, set webhook_url when you register. We POST the AuthResult JSON to you within ~2s of the user's tap, HMAC-signed with your webhook_key.
POST https://your-bank.example.com/auth/webhook
X-Zoza-Signature: sha256=<hex HMAC of body>
{
"challenge_id": "ch_...",
"app_id": "app_...",
"user_id": "user_rahul",
"device_id": "iphone_15_pro",
"device_name": "iPhone 15 Pro",
"status": "approved",
"context": "payment",
"verified_at": "2026-04-17T14:22:51Z"
}
Webhook body carries an HMAC signature over the JSON. An incrementing nonce field is on the roadmap (ETA ~3 dev days). Until then, dedupe idempotently on challenge_id — every challenge is one-shot.
Error codes
| HTTP | Meaning | When |
|---|---|---|
| 200 | OK | Status fetch / respond / audit read. |
| 201 | Created | App, device, or challenge created. |
| 400 | Bad Request | Malformed body, bad hex key, bad TTL, missing user_id. |
| 401 | Unauthorized | Missing / wrong Bearer token. |
| 403 | Forbidden | Verify-step failed: public-key mismatch, wrong device, invalid DH proof. |
| 404 | Not Found | Unknown app, device, or challenge ID. |
| 409 | Conflict | Challenge already approved / denied / expired; reapprove rejected. |
| 410 | Gone | Challenge TTL elapsed before respond. |
| 429 | Too Many Requests | Rate limit: per-user challenges/min, or per-email apply submissions. |
| 500 | Server Error | Bug on our side — email hello@zoza.world with the request ID. |
Plans & rate limits
Free
- REST API + webhooks
- 1 registered app
- Community support
Pro
- Everything in Free
- Email support (24h SLA)
- Audit-log API retention 90 days
- Multiple registered apps
Enterprise
- Everything in Pro
- SLA + on-call
- Dedicated HSM / threshold server key
- On-prem / VPC option
- Custom compliance (RBI, HIPAA, GDPR)
Going-live checklist
Support
| hello@zoza.world — integration, key requests, enterprise pricing | |
| Security | security@zoza.world — bug bounty submissions (scope + payouts) |
| Status | auth-api.zoza.world/health — live uptime |
| Audit | Audit policy · Canary · Retention · Bounty |
| @zoza_world — launches, incidents, roadmap | |
| Source | Source-available. Mobile SDKs + threshold server key in flight. Integration today is via REST. |
© Zoza · zoza.world · Auth is source-available; mobile SDKs + threshold key ship post-launch.