Migrate from Prelude to Bird.

Verify is one channel of many on Bird. If you also send email, SMS, WhatsApp, or voice from your app, the consolidation is real. If verify is all you need — that's in the honest-note section at the bottom. Read that first.

Prelude verify to Bird Verifications.

The shapes are close. Bird returns the verification by id rather than re-keying off the phone number, and surfaces the reason codes behind the risk score.

PreludeBirdNote
prelude.verification.create({ target, signals })bird.verifications.start({ to, channel, fallback, signals })Same fraud signals accepted. Fallback channel is a single attribute.
prelude.verification.check({ target, code })bird.verifications.check({ id, code })Bird checks by verification id, not phone — safer when the user has multiple in flight.
verification.metadata.risk_score (0-100)data.risk_score (0-100) + data.risk_reasons[]Bird returns reason codes alongside the score.
verification.metadata.routing_decisiondata.route (carrier, latency_estimate, cost_estimate)Bird surfaces the routing choice; Prelude keeps it internal.
webhook event: verification.success / verification.failureverification.completed / verification.failedSame envelope as other Bird channels.
prelude.lookup (number intelligence)bird.lookup.get({ phone })Bird Lookup is a sibling API; same auth, same shape.

The verification flow, both shapes.

Same recipient, same code, same outcome. Bird's contract destructures { data, error } and surfaces both the risk score and the reason codes alongside.

prelude-verify.ts
before
import { Prelude } from "@prelude.so/sdk";

const prelude = new Prelude({ apiToken: process.env.PRELUDE_API_TOKEN! });

// 1. Start a verification.
const verification = await prelude.verification.create({
  target: { type: "phone_number", value: "+14155550172" },
  signals: {
    device_id: "device_abc",
    ip:        "203.0.113.42",
  },
});

console.log(verification.id);
console.log("risk_score:", verification.metadata.risk_score);

// 2. Check the code the user typed.
const check = await prelude.verification.check({
  target: { type: "phone_number", value: "+14155550172" },
  code:   userInputCode,
});

if (check.status !== "success") {
  throw new Error("Verification failed");
}
bird-verify.ts
after
import { BirdClient } from "@bird/sdk";

const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

// 1. Start a verification.
const { data: v, error } = await bird.verifications.start({
  to:       "+14155550172",
  channel:  "sms",
  fallback: "voice",
  signals:  { device_id: "device_abc", ip: "203.0.113.42" },
});

if (error) throw error;
console.log(v.id);
console.log("risk_score:", v.risk_score);
console.log("reasons:",    v.risk_reasons);
// → ["new_device", "no_velocity_signal"]

// 2. Check the code the user typed.
const { data: result, error: e } = await bird.verifications.check({
  id:   v.id,
  code: userInputCode,
});

if (e) throw e;

Same inputs, similar score, different return contract.

Both APIs take the same fraud signals. The 0-100 risk score is comparable in shape. The main practical difference: Bird returns risk_reasons — the named factors that contributed to the score — so you can branch on them in your decision logic.

SignalPreludeBird
risk_scoreinteger 0-100integer 0-100
risk_reasonsnot exposedarray of reason codes (e.g. new_device, velocity_spike)
device_idacceptedaccepted
ipacceptedaccepted
user_agentacceptedaccepted
session_idacceptedaccepted
velocity trackingmanaged internallyreturned in risk_reasons when triggered

Seven days of parallel verification before the canary.

Prelude stays the primary — the user-visible outcome (success / failure / code) is decided by Prelude. Bird runs as a non-blocking mirror so you can compare the score distributions, per-route delivery rates, and check-success rates on real traffic.

dual-write.ts
parallel
// 7-day dual-write window. Prelude stays the primary; Bird is the mirror.
// Compare risk_score distributions and per-route delivery side-by-side
// before flipping.
import { Prelude } from "@prelude.so/sdk";
import { BirdClient } from "@bird/sdk";

const prelude = new Prelude({ apiToken: process.env.PRELUDE_API_TOKEN! });
const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! });

export async function startVerification(
  phone: string,
  signals: { device_id: string; ip: string },
) {
  // Primary path.
  const primary = prelude.verification.create({
    target:  { type: "phone_number", value: phone },
    signals,
  });

  // Mirror to Bird; tag the cohort so the dashboard groups it.
  const mirror = bird.verifications.start({
    to:       phone,
    channel:  "sms",
    fallback: "voice",
    signals,
    tags:     { migration_cohort: "dual-write" },
  });

  // Don't block the live path on the mirror.
  const [primaryResult] = await Promise.all([
    primary,
    mirror.catch(() => null),
  ]);

  return primaryResult;
}

Eight steps from first key to retired token.

End-to-end this takes 14–21 days on a steady production stream. The dual-write hold dominates — rushing it hides the corners where the two scores disagree.

  1. 01Provision a Bird production API key. The same key works against Verifications, Lookup, and the other channels.
  2. 02Port the verification start and check calls; wire them behind a feature flag in your auth path.
  3. 03Implement the dual-write helper in your verify service. Prelude stays primary; Bird runs as a non-blocking mirror.
  4. 04Deploy dual-write. Validate that both providers return success for the test recipient and that risk_score distributions are within a few points on the same traffic.
  5. 05Hold dual-write for at least 7 days. Compare per-country, per-carrier delivery rates and check-success rates on the Bird dashboard.
  6. 06Flip the canary: 10% of verifications Bird-primary for 24 hours, then 50% for 24 hours, then 100%.
  7. 07Keep the Prelude mirror enabled for 7 more days as the rollback path. Watch the Bird dashboard for divergence.
  8. 08Disable the Prelude mirror, rotate the Prelude API token to a parking value, archive the old code path.

When not to migrate.

If you don't need email, SMS, WhatsApp, or voice from the same vendor, Prelude is sharp on what it does. The team there is verify-specialized; their routing engine and fraud-detection ML are built for one job, and they do it well. Migrate to Bird if you want one platform for all messaging — same auth, same idempotency contract, same webhook shape across verifications, sends, lookups, and inbound. If verify is your whole need, stay where you are. We'd rather you tell us that on day zero than discover it at the cutover.

Migrate if Bird buys you platform consolidation.

Email migrations@bird.com with your monthly verification volume and the channels you also use (or want to use). We'll provision a shared channel and a verify-engineer for the dual-write window.

Start with one channel.
Add the others when you're ready.

A test API key is yours immediately. Production unlocks when you add a payment method and verify a sender.

Get startedRead docsor

Using Claude Code, Cursor, or Codex? Point it at our hosted MCP server: curated Bird tools, a browser sign-in, and no API key. Or install the bird-ai plugin.

Cursor