Dev Mode & Test Pairs
Dev mode lets you drive a complete verification flow — request, deliver, verify — without sending a single message to anyone. You get a temporary test pair: a synthetic phone number and a matching OTP. Send the OTP request to that number, type that code, and the transaction succeeds exactly as a real one would.
Nothing is sent, nothing is billed
A test-pair verification is synthesized server-side. No WhatsApp, SMS, Telegram or email message is dispatched, no charge is applied, and no provider delivery or delivery-status webhook is forwarded. Run it as many times as you like — nothing is sent or billed.
V1.2 callbacks are delivered unsigned — confirm the result
The direct V1.2 API callback body contains mainTransaction and transactionReq. Dev-mode state is carried on those transaction objects. Treat delivery as the signal and confirm the outcome with a server-side check on transactionID — never grant access off an unauthenticated POST to your callback URL alone.
- Name
Payload- Type
- object
- Description
- The callback is an object with sanitized
mainTransactionand thetransactionReqobject; it has no top-level status envelope.
- Name
Dev-mode marker- Type
- boolean fields
- Description
- For a test-pair transaction,
mainTransaction.isTestandtransactionReq.isTestare true.
- Name
Signature- Type
- no generated svix headers
- Description
- The direct API call sites omit
signingSecretwhen invokingsendBackendCallback, so this path does not generatesvix-*headers.
V2 Widget callbacks are signed
The V2 Widget callback is an envelope with top-level status and timestamp, plus attempt, transaction, metadata, and optional error data.
- Name
Payload- Type
- object
- Description
- The envelope contains
status,timestamp,widgetAttempt,transaction,transactionReq,publicMetadata, andprivateMetadata; failed callbacks may also includeerror.
- Name
Dev-mode marker- Type
- widgetAttempt.isTest
- Description
- A test-pair attempt is marked by
widgetAttempt.isTest. It is not a top-level webhook field.
- Name
Signature- Type
- svix headers
- Description
- The widget success path supplies the pipeline signing secret to the common dispatcher, which generates
svix-id,svix-timestamp, andsvix-signature.
Dev mode is a dashboard control, not an API
You enable dev mode and rotate its test pair from the Akedly dashboard. There is no public API-key endpoint for turning it on — this is deliberate, so a leaked production key can never enable a verification bypass.
How it works
Enable dev mode on the surface you're integrating against:
- V1.2 REST API → enable it on the pipeline. The dev-mode panel is only rendered for v1.2 pipelines; it does not appear on other pipeline versions.
- V2.0 Widget SDK → enable it on the widget.
A pipeline attached to a widget cannot use pipeline dev mode
If the pipeline backs a widget, the server refuses to enable pipeline dev mode on it. Enable dev mode on the widget instead — that is the surface the attempt actually runs through.
Akedly then mints a test pair and holds it for a 60-minute window. While that window is open, any verification request whose phone number matches the test pair's number is treated as a test: the stored test OTP becomes the transaction's OTP, and the whole delivery path is skipped.
Any other phone number sent to the same pipeline or widget during that window behaves completely normally — real code, real delivery, real billing. Dev mode does not put your integration into a global test state; it only special-cases the one number. (One exception: V1.2's optional bypass toggles, which are pipeline-wide — see Proof-of-work and Turnstile below.)
The window expires — it does not renew
After 60 minutes the pair stops matching and requests to that number fall through to the normal path. Re-enable or rotate from the dashboard to get a fresh window. Rotating always mints a new phone number and a new OTP — the previous pair stops working immediately.
Choosing the OTP length
When you enable or rotate a test pair, the OTP can be 4, 5 or 6 digits. This is the length you will actually type, and it is the length the transaction is created with — so it is how you test a non-default digit length end to end.
Pick the length when you rotate the pair
The dashboard's test-pair panel carries a length selector, so 4, 5 and 6 are all reachable without touching the API — choose one and rotate the pair to mint it.
- Name
Length- Type
- 4 | 5 | 6
- Description
The number of digits in the minted test OTP. Defaults to
6.
- Name
Scope- Type
- per rotation
- Description
The length is a property of the pair you just minted, not a saved pipeline or widget setting. Rotate again at a different length and the new pair simply has a different length.
Because the length lives in the pair itself, the rule is simply: the test pair's OTP length is the length. There is no separate setting that could drift out of sync with the code you were given.
V2 widgets adapt automatically
For a V2 Widget attempt against a test pair, Akedly derives the attempt's
digits from the stored pair, and the hosted widget renders that many input
boxes. Rotate the pair to 4 digits and the widget shows a 4-box input on the
next attempt — no configuration and no code change on your side.
The stored pair wins — but validation still runs first
On a test-pair request the OTP and its length always come from the stored pair: a
valid digits value that differs from the pair's length is not applied, and any
custom otp you pass is ignored. Request validation still runs first — on V2
an out-of-range digits or a mismatched otp is rejected with
INVALID_OTP_DIGITS / OTP_DIGITS_MISMATCH before the test pair is even
considered, while V1.2 keeps its lenient posture and falls back to the default. To
test a different length, rotate the pair to that length.
What is skipped
A test transaction deliberately bypasses the machinery that would otherwise have real-world side effects:
| Skipped | Why it matters |
|---|---|
| Message delivery | No WhatsApp, SMS, Telegram or email is sent — the successful delivery is synthesized for display. |
| Billing | The transaction carries no billing state, so repeated testing costs nothing. |
| Meta delivery-status forwarding | metaWebhookForwardUrl is not attached, so provider/Meta delivery-status traffic is not forwarded for test traffic. This is not your back-end callback URL — see below. |
| Operational alerts | Test transactions do not raise the internal success/failure notifications real traffic does. |
| Rate limiting & circuit breaker | Skipped only for requests matching the test pair — the server flags a request as test traffic when dev mode is on and the phone number matches, and only that flag short-circuits the two middlewares. Every other request to the pipeline stays fully protected. Test traffic is also excluded from the counters and buckets they measure, so a tight test loop neither consumes your budget nor trips the breaker for real users. |
The V2 widget omits isTest on its failure redirect
Which redirects carry isTest=true differs by surface. On the V1.2 REST
path both the success and the failure redirect carry it. On the V2 widget,
only the success redirect does — its failure redirect carries status,
error, transactionId, attemptId and timestamp, but no isTest. So on
the widget, never read a missing isTest on a failed redirect as proof the
attempt was real traffic. The webhook payload carries its surface-specific
flag on test attempts: mainTransaction.isTest for V1.2 and
widgetAttempt.isTest for V2.
Proof-of-work and Turnstile are handled differently
These two are not simply "off in dev mode", and they differ by surface:
- V2.0 Widget SDK — proof-of-work is skipped for the test-pair attempt only; real traffic against the same widget keeps full protection. Turnstile is still required for test attempts, but the hosted widget completes it for you, so it costs you nothing.
- V1.2 REST API — proof-of-work and Turnstile still apply to the test number. If your test client cannot solve them, the dev-mode panel offers two explicit opt-in toggles, Bypass PoW and Bypass Turnstile.
The panel on a v1.2 pipeline carries three switches: Enable Dev Mode, which opens a one-hour window in which the test number skips rate limiting and the circuit breaker; Bypass PoW, for when proof-of-work blocks your test client; and Bypass Turnstile, useful when testing from localhost without a registered domain. Note the asymmetry — the rate-limit and breaker skip applies only to the test number, while the two bypass toggles apply pipeline-wide.
The V1.2 bypass toggles are pipeline-wide
Bypass PoW and Bypass Turnstile are not scoped to the test number. While the dev-mode window is live they disable that check for every request to the pipeline. They are off by default and become inert as soon as the window expires or dev mode is switched off — but never enable them on a pipeline that is serving production traffic.
Using it
- Enable dev mode on the pipeline (V1.2, and only if it is not attached to a widget) or on the widget (V2) in the dashboard, choosing an OTP length of 4, 5 or 6. The window runs for one hour.
- Copy the test pair — the dashboard shows the synthetic phone number and its OTP, with separate Copy Phone number and Copy OTP buttons.
- Start a verification for that phone number exactly as you would for a real user.
- Submit the test OTP. The transaction verifies and nothing is sent or billed — but your back-end callback URL is still called, with
mainTransaction.isTeston V1.2 orwidgetAttempt.isTeston V2.
Never ship a test pair
The test number and its code are development aids. Do not hard-code them into application logic, and do not leave dev mode enabled on a pipeline or widget serving production traffic — while the window is open, anyone who knows the pair can complete a verification for that number.
Related
- V1.2 REST API — the
digitsparameter and its validation. - V2.0 Widget SDK — attempt creation and
attemptInfo.digits.
