Skip to content

Payer phone verification

The separate payer identity contract accompanies implemented local endpoints and a separate payer SDK, generated in generated/payer-typescript; its sandbox server is http://127.0.0.1:18190, on your machine only: the hosted sandbox does not run the payer API yet. Merchant clients retain their existing API and base URL. No real SMS is sent, and verifying a phone grants no access to an issuer account.

Create a challenge with POST /v1/payer/phone-challenges, an Idempotency-Key UUID and {phone, public_key, locale}: a configured synthetic +243 number, the device’s public key and the locale fr, ln, sw or en. The answer gives the masked number, the expiry and signing_bytes. Verify it with POST /v1/payer/phone-challenges/{id}/verify, another Idempotency-Key UUID and {code, signature}: the six-digit code from the private synthetic delivery and the signature described below. Never log the full number, the code, the signature or the grant. After an uncertain response, retry with the same body and key: the original answer returns, without a new code, another attempt or a later expiry. A replayed creation answer is the original record, not a live status read: always check its expiry. A new challenge the limits allow replaces the previous code; nothing is resent on its own. JSON bodies are limited to 4096 bytes, unknown fields/scalar coercion are rejected, and responses are not cacheable.

Use a named P-256 DER SubjectPublicKeyInfo public key encoded as canonical unpadded base64url. Decode the returned signing_bytes, sign those exact bytes using SHA256withECDSA, and return the DER signature as unpadded base64url together with the phone code. Raw JOSE r||s signatures are a different format. The server loads immutable challenge data; it does not accept caller-supplied nonce, binding or timestamps.

Codes and device proof expire together at five minutes. Five wrong codes lock a challenge; ten failed codes per phone per hour also limit replacement challenges. Resends require 60 seconds and preserve rolling phone/device/source limits. Bad signatures have their own persistent proof budget. Exact mutation retries preserve their original result; changed requests conflict. A successful result creates one two-minute enrollment grant, never a payer session. Store neither codes nor grants in logs or analytics.

The private synthetic delivery viewer is service-key protected, audited, and absent outside the local sandbox. It is not part of this public SDK. Production SMS, recovery, device attestation, HTTPS and approved risk controls remain gates. Independent Android and iOS enrollment/session/account-link journeys follow the accepted identity contracts; no shared application logic is introduced.

Profile enrollment follows this phone grant; see payer enrollment. It creates no session.