Guides / Twilio Verify

How to test Twilio Verify without sending real SMS

Twilio Verify is convenient in production because Twilio creates, sends and checks the code for you. In tests that's exactly the problem: your code never sees the code.

Updated October 8, 20266 min read
Short answer

Point the Twilio SDK at a Verify-compatible mock in your test environment. The mock generates the code, exposes it to your tests over HTTP, and approves verificationChecks.create when the right code is submitted. Your application code stays the same.

Why Verify is harder to test than plain SMS

With Programmable Messaging, your app writes the message, so a test can intercept the text. With Verify, your app only calls two endpoints:

The code itself only exists inside Twilio and on the user's phone. A fake that stubs messages.create doesn't help, and stubbing both Verify calls to always return approved means your test never exercises a wrong-code path.

What about Twilio's test credentials?

Twilio's test credentials are designed for a small set of resources: Messages, Calls, IncomingPhoneNumbers and Lookup. Requests to other resources are rejected, so they don't cover Verify, and they don't give you a message you can read back either.

Using a Verify-compatible mock

otpmock implements both Verify endpoints with Twilio's paths, parameters and response shapes:

Connect the SDK

src/sms.js
import twilio from "twilio";
import { OtpMockHttpClient } from "./twilio-node-client.mjs";

export const sms = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN, {
  httpClient: process.env.OTPMOCK_URL ? new OtpMockHttpClient(process.env.OTPMOCK_URL) : undefined,
});

// unchanged application code
await sms.verify.v2.services(VERIFY_SID).verifications.create({ to: phone, channel: "sms" });
const check = await sms.verify.v2.services(VERIFY_SID).verificationChecks.create({ to: phone, code });

In the test environment, set TWILIO_AUTH_TOKEN to your otpmock API key. The client keeps every path and rewrites the host, so both api.twilio.com and verify.twilio.com traffic goes to otpmock.

Read the code in a test

verify.spec.ts
const since = Date.now() - 5_000;
await page.getByRole("button", { name: "Send code" }).click();
const { code } = await otp.waitForCode(phone, { since });

// happy path
await page.getByLabel("Code").fill(code);

// or test the wrong-code path first
await page.getByLabel("Code").fill(code === "000000" ? "111111" : "000000");
await expect(page.getByText("Incorrect code")).toBeVisible();

FAQ

Does my Verify Service SID need to exist?

No. otpmock accepts any Service SID and keeps verifications separate per SID, so you can use the same SID as in production or a placeholder.

Which channels are supported?

The channel parameter is accepted and echoed back, and the code is delivered to the inbox for every channel. Use sms in tests.

Is the official twilio-node SDK supported?

Yes. otpmock is tested against the official twilio package for Node.js, version 5, for both Programmable Messaging and Verify.

Test Verify flows today

The free plan includes 100 messages a month; each verification started counts as one.

Get a free API key

Related guides