Bird vs Prelude
Bird vs Prelude for Verify
Prelude is built around a risk verdict you can branch on, and Bird has no equivalent to it. The two APIs are otherwise close enough that porting is mostly a rename. This page is about which of those two facts matters more for what you are building.
What Prelude is great at.
Where Bird is different.
What Prelude is great at
A verdict you can branch on. The create call accepts IP, device and fingerprint signals and answers with a routing verdict, with risk factors when it refuses. Bird's create answers with the verification itself: there is no verdict, no signals object, and no shadow block. A signup that gates on that decision has to make it before it calls Bird.
Channels Bird does not reach. A voice call, RCS, Viber, Zalo and silent network authentication, across 230+ countries. Both platforms carry SMS, WhatsApp and Telegram, so the gap is those five rather than the whole mix: a number Prelude reached over Viber or Zalo falls back to SMS here, which is a delivery-rate question worth measuring in a pilot rather than discovering at full volume.
The message and the fallback are yours to shape. A template with variables, a locale, your own sender ID and a custom code are all per-request, and max_auto_fallbacks and force_challenge tune the escalation per call. On Bird the passcode text is Bird's and the fallback is the country's channel plan; senders are configured per channel rather than per request, and only the email one can be an address of your own.
Where Bird is different
A failed check says which kind of failure it was. Prelude folds a wrong code and exhausted attempts into one failure status. Bird answers with a reason of incorrect_code, expired or attempts_exhausted, and returns attempts_remaining beside it, so the screen can tell a user how many tries are left without counting them yourself.
Delivery events are a workspace subscription, signed. Prelude posts to a callback_url you set per verification. Bird's endpoints are registered once, each subscribed to the event types it wants, and every delivery is signed per Standard Webhooks, so the URL leaves the request body and the receiver has something to verify.
An agent can run the flow. Bird's hosted MCP server gives an agent three verification tools against a real workspace: start one, check a code, and advance to the next channel. It cannot reconfigure the policy behind them, which is deliberate rather than an omission.
The matrix
Capability by capability.
The two APIs are shaped alike, so most rows are even and the differences are narrow. Read the safe-retries row against the Twilio Verify page: it is a Bird win there and even here, because Prelude has a deduplication mechanism and Twilio Verify documents none. Prelude's risk layer is a concession above rather than a row, because Bird ships nothing to compare it against.
| Capability | Bird | Prelude | Who wins? |
|---|---|---|---|
| Create request | JSON to /v1/verify/verifications with a bearer key. The recipient is to.phone_number or to.email, and calling create again for a live recipient retries rather than starting a new verification. | JSON to a v2 verification endpoint with a bearer key. The recipient is target.type and target.value, and calling create again for a live recipient retries in the same way. | |
| Safe retries | An Idempotency-Key header on the create makes a retry safe. | A dispatch_id on the create deduplicates a repeated request. It is a field on the body rather than a header, and it does the same job. | |
| What a failed check tells you | success is false with a reason of incorrect_code, expired or attempts_exhausted, and attempts_remaining beside it. | A status of failure covers both a wrong code and exhausted attempts, with expired_or_not_found as a separate value. The two failure kinds are not distinguished. | |
| Channels a passcode can arrive on | Email, SMS, WhatsApp and Telegram. Voice is labelled Rolling out rather than shipped. | Their published channel mix is SMS, voice, RCS, WhatsApp, Telegram, Viber, Zalo and silent network authentication, across 230+ countries, with automatic fallback between them. | |
| Delivery events | A workspace webhook subscribed to the verify event types you name, such as verify.verification.verified and verify.attempt.delivered, signed per Standard Webhooks. | A callback_url set per verification on the create request, so the destination travels with each call. | |
| Fallback control | The country's channel plan sets the order, and a next-channel call advances one verification to the next channel in it. | max_auto_fallbacks and force_challenge tune the escalation on the create request itself. | |
| Hosted MCP server | Three verification tools on the hosted server at mcp.bird.com: start a verification, check a code, and advance to the next channel. Configuration is not among them. | Prelude publishes backend SDKs for Node.js, Python, Go, Kotlin/Java and Ruby, and mobile SDKs for iOS and Android, so the agent story is a library your own runtime calls. | |
| Code length | options.code_length on the create, else the workspace default. | options.code_size on the create. |
The same verification
Starting one verification.
The shapes are close enough that this is mostly a rename: target becomes to, code_size becomes code_length, and dispatch_id becomes an Idempotency-Key header. What does not appear on the Bird side is the signals object, and there is no verdict on the response to branch on.
Prelude
const signupId = crypto.randomUUID();
const response = await fetch("https://api.prelude.dev/v2/verification", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PRELUDE_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
target: { type: "phone_number", value: "+15551234567" },
dispatch_id: signupId,
options: { code_size: 6 },
}),
});
const verification = await response.json();
console.log(verification.id, verification.status);
Bird
import { BirdClient } from "@messagebird/sdk";
const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });
const signupId = crypto.randomUUID();
const { data, error } = await bird.verify.verifications
.create(
{
to: { phone_number: "+15551234567" },
options: { code_length: 6 },
},
{ idempotencyKey: signupId },
)
.safe();
if (error) console.error(error.message);
else console.log(data.id, data.status);
Switching cost
Low, unless you use the risk layer.
Both APIs key on the recipient rather than a verification ID, so the calls port almost as they stand: target.value becomes to.phone_number or to.email, code_size becomes code_length, dispatch_id becomes an Idempotency-Key header, and the per-verification callback_url becomes a workspace webhook subscribed to the verify event types you name.
The risk layer is the part that is not a port at all. If you branch on Prelude's verdict, send signals with the create, or rely on a shadow block, there is nothing on the Bird side to move that logic to and the decision has to happen before the call. Size that gap first, because everything else here is an afternoon.
Questions people actually ask
Is Bird a good Prelude alternative?
What happens to Prelude's signals and verdict?
Do I lose safe retries by moving?
Which channels do I lose?
Where to go next
The migration guide is the one to start with: it maps the API field by field.