Skip to content
Akedly
Testing & going live

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.

Run a test

  1. Enable dev mode and choose an OTP length.

    Do it in the dashboard, on the pipeline (V1.2, and only if it is not attached to a widget) or on the widget (V2), choosing an OTP length of 4, 5 or 6. The window runs for one hour.

  2. Copy the test pair.

    The dashboard shows the synthetic phone number and its OTP, with separate Copy Phone number and Copy OTP buttons.

  3. Start a verification for that number.

    Start a verification for that phone number exactly as you would for a real user.

  4. Submit the test OTP.

    The transaction verifies and nothing is sent or billed — but your back-end callback URL still receives the legacy verification-result callback, with mainTransaction.isTest on V1.2 or widgetAttempt.isTest on V2. Test transactions do not emit signed OTP channel events. The body is shown in the callback your server receives.


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.
  • Name
    The test pair
    Type
    +1555 number and an OTP
    Description

    Akedly mints a test pair and holds it for a 60-minute window. The number is synthetic and starts with +1555. While the 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.

  • Name
    Other phone numbers
    Type
    unaffected
    Description

    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.)

  • Name
    The window expires — it does not renew
    Description

    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.

  • Name
    Rotating the pair
    Description

    Rotating always mints a new phone number and a new OTP — the previous pair stops working immediately.

  • Name
    A pipeline attached to a widget cannot use pipeline dev mode
    Description

    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.

  • Name
    Dev mode is a dashboard control, not an API
    Description

    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.


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. 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.

  • Name
    V2 widgets adapt automatically
    Description

    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.

  • Name
    The stored pair wins — but validation still runs first
    Description

    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.

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.


What is skipped

A test transaction deliberately bypasses the machinery that would otherwise have real-world side effects:

SkippedWhy it matters
Message deliveryNo WhatsApp, SMS, Telegram or email is sent — the successful delivery is synthesized for display.
BillingThe transaction carries no billing state, so repeated testing costs nothing.
Meta delivery-status forwardingmetaWebhookForwardUrl 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 alertsTest transactions do not raise the internal success/failure notifications real traffic does.
Rate limiting & circuit breakerSkipped 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.

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 callback your server receives

A test transaction still calls your back-end callback URL; only delivery is skipped. The body has the same shape a real result has, with the test marker set; the examples show the fields this page discusses. See Verify and callbacks on V1.2 and Results and callbacks on V2 for the full contract on real traffic.

V1.2 verification-result callbacks are signed

The direct V1.2 verification-result callback body contains mainTransaction and transactionReq. Dev-mode state is carried on those transaction objects. Verify the Svix signature on the raw body before acting; if a result ever arrives unsigned, confirm it with a server-side check on transactionID instead. This contract covers the verification-result callback; signed otp.channels.resolved and otp.channels.updated events use the separate signed channel-event contract.

  • Name
    Payload
    Type
    object
    Description
    The callback is an object with sanitized mainTransaction and the transactionReq object; it has no top-level status envelope.
  • Name
    Dev-mode marker
    Type
    boolean fields
    Description
    For a test-pair transaction, mainTransaction.isTest and transactionReq.isTest are true.
  • Name
    Signature
    Type
    svix headers, best effort
    Description
    Signed with the pipeline webhook signing secret, with svix-id, svix-timestamp and svix-signature headers. Retries reuse the same svix-id. If signing fails the result is still sent, without those headers.
  • Name
    OTP channel events
    Type
    excluded for test transactions
    Description
    A transaction marked isTest: true still receives this verification-result callback, but it does not emit signed otp.channels.resolved or otp.channels.updated events.

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, and privateMetadata; failed callbacks may also include error.
  • 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, and svix-signature.

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.