Zivo Invoice. Track expenses. See your money.
Menu

Zivo Identity API

Sign in with WhatsApp.

Add passwordless WhatsApp registration and login. Zivo verifies the number on a hosted page; your backend receives only a verified identity.

Architecture

What Zivo handles

  1. Your platform creates state and an S256 PKCE challenge, then redirects the browser to Zivo.
  2. Zivo hosts the phone form, sends the approved WhatsApp template, and verifies the six-digit code.
  3. Zivo returns a two-minute, single-use authorization code to your exact registered callback.
  4. Your backend exchanges the code and PKCE verifier for a verified phone identity.
Your platform never receives, stores, logs, or validates the WhatsApp OTP.

Step 1

Register your platform

Open Workspace → Connected Platforms. Create the client, add every exact HTTPS callback URL, enable it, and copy the client secret once.

Open Connected Platforms
ZIVO_AUTH_BASE_URL=https://zivo.co.ke ZIVO_AUTH_CLIENT_ID=zivo_your_client_id ZIVO_AUTH_CLIENT_SECRET=server_only_secret ZIVO_AUTH_REDIRECT_URI=https://your-app.example/auth/zivo/callback
Never place the client secret in browser JavaScript, mobile bundles, public repositories, URLs, or logs.

Step 2

Create state and PKCE

Create new random values per attempt and keep state plus verifier in the server session.

$state = Str::random(48); $verifier = Str::random(64); $challenge = rtrim(strtr( base64_encode(hash('sha256', $verifier, true)), '+/', '-_' ), '='); session(['zivo_auth' => compact('state', 'verifier')]);

Step 3

Open hosted verification

GEThttps://zivo.co.ke/auth/whatsapp/authorize
ParameterValue
client_idConnected Platform client ID
redirect_uriExact registered HTTPS callback
stateRandom browser-session value
code_challengeBase64url SHA-256 challenge
code_challenge_methodS256
scopephone
https://zivo.co.ke/auth/whatsapp/authorize ?client_id=zivo_your_client_id &redirect_uri=https%3A%2F%2Fyour-app.example%2Fauth%2Fzivo%2Fcallback &state=RANDOM_STATE &code_challenge=BASE64URL_SHA256 &code_challenge_method=S256 &scope=phone

Step 4

Validate the callback

Zivo returns code and the original state. Compare state before exchanging the code.

GET /auth/zivo/callback?code=zvc_SHORT_LIVED_CODE&state=ORIGINAL_STATE if (! hash_equals(session('zivo_auth.state'), request('state'))) { abort(403, 'Invalid authentication state'); }

Step 5

Exchange the code from your backend

POSThttps://zivo.co.ke/api/zivo/auth/token
$response = Http::asForm()->post( 'https://zivo.co.ke/api/zivo/auth/token', [ 'grant_type' => 'authorization_code', 'client_id' => env('ZIVO_AUTH_CLIENT_ID'), 'client_secret' => env('ZIVO_AUTH_CLIENT_SECRET'), 'code' => request('code'), 'redirect_uri' => env('ZIVO_AUTH_REDIRECT_URI'), 'code_verifier' => session('zivo_auth.verifier'), ] );

The code expires after two minutes and cannot be exchanged twice.

Response

Use the verified identity

{ "token_type": "Zivo-Verified-Identity", "scope": "phone", "identity": { "sub": "pairwise_platform_subject", "phone": "+2547XXXXXXXX", "phone_verified": true, "verified_at": "2026-08-12T12:00:00+03:00" } }
  • Match by sub first, then normalized phone for initial linking.
  • Store sub as the durable Zivo identity for your platform.
  • For new users, collect remaining profile fields and mark the phone verified.
  • Create your own session; this response is not a reusable bearer token.

Troubleshooting

Errors

ErrorMeaningAction
invalid_clientUnknown, disabled, or wrongly authenticated clientCheck credentials or rotate the secret
invalid_grantExpired/used code, wrong callback, or PKCE mismatchRestart authorization
Callback not registeredRedirect URI differs from the allowlistRegister the exact HTTPS URL
429Too many attemptsBack off before retrying

Production checklist

Security requirements

  • Use exact HTTPS callback allowlists.
  • Generate new state, verifier, and challenge for every attempt.
  • Keep secrets only on your backend and rotate exposed credentials.
  • Never log authorization codes, verifiers, client secrets, OTPs, or full identity payloads.
  • Regenerate your application session after login.
  • Use separate production and test clients.
  • Disable access immediately from Connected Platforms when required.

Reference implementation

How Dereva uses it

Dereva displays Continue with WhatsApp on login and registration. Registration preserves Driver, Employer, or Dealer choice. Existing verified phones sign into the matching account; new phones complete only the remaining account information.

Dereva receives the pairwise subject and verified phone only. Zivo controls OTP delivery, validation, retry limits, expiry, and audit history.

FAQ

Frequently asked questions

Does my platform receive the WhatsApp verification code?

No. Zivo hosts and validates the OTP. Your backend receives only a short-lived authorization code and verified identity.

Why is PKCE required when the client already has a secret?

PKCE binds the browser authorization request to the server-side exchange and adds protection against intercepted codes.

How long is an authorization code valid?

Two minutes. It is single-use and the first valid exchange consumes it.

What should happen for a new phone number?

Collect the remaining profile fields, store the Zivo subject, mark the phone verified, and create your own application session.