Skip to content
Back to skills

Source 3094b5e7c660d4bb8adbd282

BSecurity

Migrate an existing OTP / one-time-passcode flow from the Twilio Programmable Messaging API (self-built: generate code → send SMS → validate) to the Twilio Verify API (managed code generation, delivery, expiry, rate limiting, and Fraud Guard). Covers what Verify replaces, creating a Verify Service, replacing send + check logic, E.164 formatting with Lookup, bring-your-own custom codes, error-code translation, and a safe incremental cutover. Use when a developer is moving OTP/2FA delivery off ...

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
testingpythongonodetestinggitapidatabasesecurity

Works with

  • cli
  • api

Security analysis

B84/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 3 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add gutsofpratik/OpenClaude-Skills --skill source-3094b5e7c660d4bb8adbd282 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Source 3094b5e7c660d4bb8adbd282?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Source 3094b5e7c660d4bb8adbd282
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/gutsofpratik-source-3094b5e7c660d4bb8adbd282/badge)](https://www.skillsdirectory.com/skills/gutsofpratik-source-3094b5e7c660d4bb8adbd282)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: twilio-migrate-messaging-to-verify
description: >
  Migrate an existing OTP / one-time-passcode flow from the Twilio Programmable
  Messaging API (self-built: generate code → send SMS → validate)
  to the Twilio Verify API (managed code generation, delivery, expiry, rate
  limiting, and Fraud Guard). Covers what Verify replaces, creating a
  Verify Service, replacing send + check logic, E.164 formatting with Lookup,
  bring-your-own custom codes, error-code translation, and
  a safe incremental cutover. Use when a developer is moving OTP/2FA delivery off
  Programmable Messaging onto Verify, or asks "should I use Verify instead of
  sending my own SMS codes?".
---

## Overview

If you built OTP on the **Programmable Messaging API**, you are maintaining code
that Twilio Verify manages for you: random code generation, a database of codes
with expiry, resend/rate-limit logic, and fraud filtering. **Verify** replaces
that entire stack with two API calls — send a verification, check a code — and
adds one-click Fraud Guard (SMS pumping protection), managed sender pools, and
automatic A2P/compliance handling.

**What you delete when you migrate:**

| You built (Programmable Messaging) | Verify replaces it with |
|---|---|
| Buy/manage a sending number or Messaging Service | Managed sender pool (short/long code, toll-free, alpha ID) chosen per-country |
| `random.randint(...)` code generation | Built-in generation (4–10 digits, default 6) |
| DB table storing `code`, `phone`, `expires_at` | Stateless — Verify tracks the code and 10-min expiry |
| Custom resend throttling / attempt counting | Built-in rate limits (5 sends & 5 checks per number / 10 min) |
| SMS pumping / fraud filtering | Fraud Guard (geo-permissions, rate anomaly) — one click |
| A2P 10DLC campaign registration for the OTP number | Exempt — Verify traffic needs no 10DLC campaign |
| Per-channel integration (SMS, voice, WhatsApp…) | One API; change the `channel` param |
| Locale-specific message copy | Pre-screened templates in dozens of languages, auto-resolved by country |

> This skill focuses on **the migration**. For full Verify API reference (all
> channels, service config, status values), see `twilio-verify-send-otp`. To
> decide *whether* Verify fits before writing code, see
> `twilio-identity-verification-advisor`.

**When to stay on Programmable Messaging:** you need full control of the message
body/branding beyond what templates allow, custom delivery routing, or you are
sending non-OTP transactional traffic. For standard OTP/2FA, migrate to Verify.

---

## Prerequisites

- An existing Programmable Messaging OTP implementation (the thing you're replacing)
- Twilio account — New to Twilio? See `twilio-account-setup`
- Environment variables:
  - `TWILIO_ACCOUNT_SID`
  - `TWILIO_AUTH_TOKEN`
  - `VERIFY_SERVICE_SID` (created in Step 1 below)
  — See `twilio-iam-auth-setup` for credential setup
- SDK: `pip install twilio` / `npm install twilio` (same SDK you already use)
- Verify needs **no separate product activation** — just create a Service

---

## Migration steps

The self-built pattern you're replacing does three things by hand: **generate** a
random code, **store** it with an expiry, **send** it over a number you own — then
later **look up + compare + check expiry** yourself. Verify collapses all of that
into the two calls below. (The "What you delete" table above maps each piece to its
Verify replacement.)

### The Verify pattern

**Step 1 — Create a Verify Service (one-time)**

Do this once and reuse the SID. Create it in the [Console](https://1console.twilio.com/go?to=/account/__account__/us1/verify/services) or via API:

**Python**
```python
import os
from twilio.rest import Client

client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])

service = client.verify.v2.services.create(friendly_name="Acme")  # your business name — shown in the SMS
print(service.sid)  # VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx — save in environment variable as VERIFY_SERVICE_SID
```

**Node.js**
```node
const twilio = require("twilio");
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

const service = await client.verify.v2.services.create({ friendlyName: "Acme" }); // your business name — shown in the SMS
console.log(service.sid); // VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

> `friendlyName` appears in the default verification SMS (e.g. "Your Acme verification code is 123456"), so set it to your business name — not an internal label. Avoid embedding 5 or more digits in the name — it can trigger error 60200.

**Step 2 — Send the verification (replaces your existing messaging logic)**

This single `verifications.create` call replaces steps you did by hand:
Verify generates the code, tracks it with a 10-min expiry, and delivers it over a
managed sender — so whatever you used to produce the code, persist it, and send
the SMS can all go. A `status` of `pending` means the code is on its way.

**Python**
```python
verification = client.verify.v2 \
    .services(os.environ["VERIFY_SERVICE_SID"]) \
    .verifications \
    .create(to="+15558675310", channel="sms")

print(verification.status)  # pending
```

**Node.js**
```node
const verification = await client.verify.v2
    .services(process.env.VERIFY_SERVICE_SID)
    .verifications.create({ to: "+15558675310", channel: "sms" });

console.log(verification.status); // pending
```

No code generation, no DB write, no owned number. Switch channel by changing
`channel` to `"call"`, `"whatsapp"`, or `"email"` (also `"sna"` and `"auto"`).
RCS isn't a `channel` value — Verify upgrades an `sms` verification to RCS where
the device supports it and falls back to SMS automatically.

**Step 3 — Check the code (replaces your existing validation logic)**

**Python**
```python
check = client.verify.v2 \
    .services(os.environ["VERIFY_SERVICE_SID"]) \
    .verification_checks \
    .create(to="+15558675310", code="123456")

if check.status == "approved":
    grant_access()
else:
    reject()  # "pending" = wrong/not-yet-submitted, "expired" = past TTL
```

**Node.js**
```node
const check = await client.verify.v2
    .services(process.env.VERIFY_SERVICE_SID)
    .verificationChecks.create({ to: "+15558675310", code: "123456" });

if (check.status === "approved") grantAccess();
else reject();
```

> A wrong code does **not** throw — it returns `status: "pending"`. You must
> test `status === "approved"` explicitly. After approval the verification is
> single-use; re-checking it returns 404. Delete your OTP database table once
> traffic is fully on Verify.

---

## Key patterns

### Format numbers as E.164 (Verify is strict)

Programmable Messaging tolerated loose formats; **Verify requires E.164**
(`+15558675310`). If your DB stores national-format numbers, normalize them
before sending or you'll get error 60200. Use the **Lookup API** to convert user
input to E.164 and capture the ISO country code — useful for fraud allow/block
lists. See `twilio-lookup-phone-intelligence`.

### Keep your own codes (bring-your-own OTP)

If you can't rip out your existing generation logic on day one, pass your code
via `custom_code` so Verify still handles delivery, templates, and localization.

**Python**
```python
verification = client.verify.v2 \
    .services(os.environ["VERIFY_SERVICE_SID"]) \
    .verifications \
    .create(to="+15558675310", channel="sms", custom_code="123456")
```

Caveats — read before relying on this:
- **Enable it per-Service first** — it is not on by default. Set `CustomCodeEnabled=true` when creating/updating the Service via the API (`services.update(custom_code_enabled=True)`), or toggle "Enable Custom Verification Code" in the Console (**Verify → Services → your Service → General**).
- **Do not pass a custom message body.** Send only the code via `custom_code`; Verify maps it into an approved template. Custom message text returns [error 60243](https://www.twilio.com/docs/api/errors/60243).
- You are billed per **attempted** send (not per completed verification), and custom-code flows tend to generate more sends than checks (abandoned/re-requested codes) — expect higher cost than standard Verify.
- **Report the outcome so Twilio can keep deliverability high.** Verify's route-intelligence engine uses whether a code gets validated as a signal — it automatically retries different carriers/routes when codes go unvalidated or delivery underperforms. Tell Verify the result by updating the verification's status to `approved` so Twilio can keep optimizing routing:

**Python**
```python
client.verify.v2 \
    .services(os.environ["VERIFY_SERVICE_SID"]) \
    .verifications(verification.sid) \
    .update(status="approved")  # after your own code check passes
```

Treat custom codes as a transition aid, not the end state — migrating to Verify-generated codes removes all these caveats.

### Translate your error handling

Verify uses different error codes than Messaging. Map them during migration:

| Situation | Verify error | Handle by |
|---|---|---|
| Malformed / non-E.164 number, bad param | 60200 | Fix `to` format (use Lookup) |
| Too many sends to a number | 60203 | Back off; surface "try again later" |
| Too many check attempts | 60202 | Issue a new verification |
| Service SID wrong | 60212 | Check `VERIFY_SERVICE_SID` |
| Country not enabled | 60410 | Enable geo-permission in Console |
| Custom message passed | 60243 | Use `custom_code` only, not a body |

Full Verify error handling and built-in protections: `twilio-verify-send-otp`.

### Rate limits & fraud — delete your custom throttling

Verify enforces, per phone per service, out of the box: **5 sends / 10 min** and
**5 check attempts** per verification. Codes live **10 minutes** and are
immutable within that window (a re-request inside 10 min returns the *same*
code). This replaces hand-rolled resend counters. Need different limits? Define
custom [Rate Limits / Buckets](https://www.twilio.com/docs/verify/api/service-rate-limits)
on the Service. Enable **Fraud Guard** in the Console to retire your SMS-pumping
defenses. See `twilio-security-hardening` for the pumping-attack background.

### Safe incremental cutover

Don't flip 100% of traffic at once:

1. **Stand up Verify in parallel.** Keep the Messaging path live; route a small % of new OTP requests (or one region / internal users) through Verify.
2. **Dual-run the check.** If a user was issued a legacy code, validate against your DB; if issued via Verify, use `verification_checks`. Track which system issued each code so checks hit the right validator.
3. **Drain, then delete.** Once no legacy codes remain within their 10-min TTL, stop issuing them, ramp Verify to 100%, then decommission the OTP number/Messaging Service and drop the OTP DB table.
4. **Reuse one Verify Service** across the app — do not create a Service per request.

For testing without tripping rate limits during the cutover, see Twilio's
[Verify testing docs](https://www.twilio.com/docs/verify/quickstarts/test).

---

## CANNOT

- **Cannot retrieve the code Verify generated** — never returned by any API (by design for security). If your old flow logged or displayed the code, that stops working.
- **Cannot reuse your owned number as the Verify sender by default** — Verify picks from managed sender pools per country. (You *can* bring your own number/Messaging Service for specific channels/WhatsApp, but that's not the default.)
- **Cannot pass a raw custom message body** — the `customMessage` param is deprecated; raw text → error 60243. To customize the copy, use an approved template (`TemplateSid`) instead.
- **`custom_code` has strings attached** — must be enabled per-Service (Console → Verify → Services → General), bills per attempt (higher cost than standard Verify), and mandates updating Verification Status manually.
- **Cannot change the 10-minute token TTL yourself** — to change the default (adjustable between 2 minutes and 24 hours), you must contact Twilio Support to set it on your Service.
- **Cannot re-check an approved verification** — single-use; a second check returns 404.
- **Cannot send to non-E.164 numbers** — normalize first (Lookup).
- **No push webhook on completion** — poll `verification_checks` (rate-limited: 60/min, 180/hr, 250/day) or use the Verify Events API.
- **Cannot skip WhatsApp sender setup** — as of March 1, 2024 there is no shared Verify WhatsApp sender; bring your own. See `twilio-whatsapp-manage-senders`.
- **Verify traffic is A2P-exempt, but your remaining non-OTP Messaging is not** — keep 10DLC registration for any other traffic on that number.

---

## Next Steps

- **Full Verify API reference (all channels, service config):** `twilio-verify-send-otp`
- **Decide product fit / architecture first:** `twilio-identity-verification-advisor`
- **Normalize numbers to E.164 + fraud signals:** `twilio-lookup-phone-intelligence`
- **Bring a WhatsApp sender for WhatsApp OTP:** `twilio-whatsapp-manage-senders`
- **Retire SMS-pumping defenses safely:** `twilio-security-hardening`
- **Credential setup:** `twilio-iam-auth-setup`
- **Source blog post:** [Migrate from Programmable Messaging to Verify](https://www.twilio.com/en-us/blog/migrate-programmable-messaging-to-verify)

Files in this skill

  • PROVENANCE.json203 B
  • SKILL.md13.1 KB
  • UPSTREAM-LICENSE.txt1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.