Migrate from Twilio Verify

Two calls become two calls. The main differences: no Verify Service to create, no phone number to buy, and you're billed only when a code actually verifies.

Send a code

js
// Twilio
await twilio.verify.v2.services(SERVICE_SID)
  .verifications.create({ to: phone, channel: 'sms' });

// SimpleSMS
await fetch('https://api.joinsimplesms.com/v1/verify', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.SIMPLESMS_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ to: phone }),
});

Check a code

js
// Twilio
const check = await twilio.verify.v2.services(SERVICE_SID)
  .verificationChecks.create({ to: phone, code });
if (check.status === 'approved') { /* ... */ }

// SimpleSMS
const res = await fetch('https://api.joinsimplesms.com/v1/verify/check', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.SIMPLESMS_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ to: phone, code }),
});
const { verified } = await res.json();
if (verified) { /* ... */ }

In Python

python
# Twilio
client.verify.v2.services(SERVICE_SID).verifications.create(to=phone, channel="sms")
check = client.verify.v2.services(SERVICE_SID).verification_checks.create(to=phone, code=code)
ok = check.status == "approved"

# SimpleSMS: pip install joinsimplesms
from joinsimplesms import SimpleSMS
sms = SimpleSMS()  # reads SIMPLESMS_API_KEY
sms.verify.send(to=phone)
ok = sms.verify.check(to=phone, code=code)["verified"]

What maps to what

TwilioSimpleSMS
toto (or phone, both work)
Account SID + Auth Tokenone API key
Verify Service SIDnothing; no service to create
A purchased phone numbernothing; we send from our pool
check.status === 'approved'verified === true
status: 'pending'verified: false, status: 'pending'
404 on bad code200 with verified: false (a wrong code isn't an exception)
Fraud GuardShield, always on
~$0.05 per attempt$0.025, only when verified

Things that get simpler

  • No Verify Service. Delete the VA... SID from your config.
  • No number. Delete the number provisioning step entirely.
  • Billing follows success. Twilio charges per verification attempt; we charge when verified comes back true, so pumping attacks and abandoned signups cost you nothing.
  • Attempt/expiry state is in the response. attempts_remaining and expires_in let you render "2 tries left" and a countdown without tracking anything yourself.

Things to watch

  • US and Canada only right now. If you verify internationally, keep Twilio for those routes or talk to us.
  • The SDK is optional. The plain fetch calls above are the whole integration; npm install joinsimplesms adds typed errors, retries, and the CLI.
  • Sandbox has no resend cooldown so you can iterate; live enforces 60 seconds per number.