One-time verification (OTP)
Fabric’s verify resource sends a one-time code over SMS and checks it for you, so you never store or compare codes yourself.
-
Start a verification
const started = await fabric.verify.start({ to: "+233201234567" },{ idempotencyKey: verificationAttemptId },);// { id, status, to, channel: "sms", expiresIn, expiresAt, debugCode? }Persist
started.data.idagainst the user’s session. Use absoluteexpiresAtfor the deadline; unlike the convenienceexpiresInduration, it stays exact after a replay. In sandbox,debugCodereturns the code so you can test without a handset — it is never present on a live verification.Reuse the same stable key when retrying one logical attempt. Fabric replays the original verification reference instead of sending and charging for a second code.
-
Check the submitted code
const result = await fabric.verify.check({ id, code: userInput });if (result.data.verifiedAt !== null) {// verified — verifiedAt holds the timestamp; result.data.status reflects the outcome}
Send your own wording
Section titled “Send your own wording”By default the code arrives in Fabric’s wording. To send your own, pass template — the stable key
of a released SMS message definition — with any variables it declares:
await fabric.verify.start({ to: "+233201234567", template: "merchant.otp", variables: { merchant_name: "Jasper's Market" }, locale: "fr-FR",});The template is chosen per call, not configured per workspace, so one integration can send
differently-branded codes for each merchant it serves. Requires SDK 0.1.0-beta.9 or later.
- Use an application-scoped key. The key names the environment the definition was released in. A
definition released into a different application’s environment answers
verify_template_not_released— the single most common way this fails first time. code,expires_minutesandexpires_secondsare Fabric’s. Your template renders them, but passing one as a variable is a400: the code is generated server-side and never accepted from a caller. Variable names reach the API exactly as your template declares them.- The definition must be transactional and contain
{{code}}. Both are checked when the code is rendered, before the message is accepted or the wallet is touched, so an ineligible template costs nothing.
Authoring definitions is a dashboard task — an sk_* key cannot create them.
Do it safely
Section titled “Do it safely”- Cap attempts. Limit how many times a user may submit a code for one
id, and how many codes a user or IP may request in a window. Verification is a common abuse target. - Do not reveal account existence. Show the same “we sent a code” response whether or not the number maps to an account. Branch on the verify result, not on whether the account exists.
- Never log the code. Treat the OTP like a password. Log the verification
idand outcome, not the code itself. - Respect expiry. After
expiresIn, start a fresh verification rather than reusing the oldid.
Transactional class bypasses quiet hours
Section titled “Transactional class bypasses quiet hours”An OTP is transactional, so it is delivered around the clock — it is not subject to the promotional quiet-hours window described in compliance. Keep verification traffic on the transactional class; sending OTPs as promotional risks them being held or blocked.