Migrate SMS from Plivo
This page maps Plivo's Message API, Powerpacks, and delivery callbacks to Bird. Follow the main migration guide in order and use these mappings for steps 3, 4, and 5.
The send is the easy part. Both take JSON with lowercase field names, and both keep 10DLC registration beside the send rather than on a separate host. Two things change. Plivo's POST https://api.plivo.com/v1/Account/{auth_id}/Message/ authenticates with an Auth ID and Auth Token over HTTP Basic; POST /v1/sms/messages takes a bearer API key against your regional host, with no account segment in the path. And a Plivo Powerpack bundles a number pool, sticky sender behaviour and opt-out state into one object; Bird splits those across senders, suppressions and keyword rules, so there is nothing to recreate as a Powerpack.
Hand this to your agent
Use this brief in your coding agent. It starts with discovery and produces a reviewable migration plan before any production change.
Code example
Help me migrate my SMS integration from Plivo to Bird.
1. Inspect this repository's sends, senders, callbacks, schedules, templates, opt-outs and tests. List the traffic and behavior that must survive the migration.
2. Read the Markdown guides at https://bird.com/docs/guides/sms/migrate/plivo.md and https://bird.com/docs/guides/sms/migrate.md. Use an existing authenticated Bird MCP or CLI connection. If neither is available, follow https://bird.com/docs/ai/set-up-your-agent.md. Discover the actual operations; do not invent commands or ask me to paste credentials into chat.
3. Prepare the code changes, sender/destination requirements, consent migration, webhook verification and rollout/rollback plan. Preserve the scope of each customer's preferences, including requests outside SMS replies. Separate API batches from audience broadcasts and preserve any behavior that has no direct endpoint equivalent.
4. Show me the exact affected resources, destinations, test volume and known costs before an action that sends messages, spends money, registers or changes a sender, or moves production traffic. Require explicit human authorization for each paid submission or production change. Name one-off 10DLC registration and resubmission fees before requesting approval. An existing explicit approval for that exact action is sufficient; broad migration approval is not. Simulated SMS destinations are billable and still require authorization.
5. If I am keeping Plivo numbers, prepare the human support port request and obtain authorization to send it. Read bird support-tickets create --help, then use the available CLI or MCP support operation with the reviewed number list and requirements. Return the ticket ID and follow the reply; support arranges the port on its own schedule, separately from the code cutover.
6. Run local and intercepted tests first. When authorized, perform the agreed bounded integration tests, inspect accepted and final outcomes separately, and report failures or uncertainty. Do not claim a delivery receipt proves reading or that request idempotency guarantees exactly-once delivery.
7. Keep production cutover and retiring the old provider as explicit steps in the approved rollout. Finish with the diff, evidence, unresolved requirements and the next action.Map the send call
| What it does | Plivo | Bird |
|---|---|---|
| Recipient | dst | to (one per request) |
| Sender | src or powerpack_uuid | from |
| Body | text | text |
| Channel selector | type: sms, mms, whatsapp | the endpoint itself; /v1/sms/messages is SMS |
| Intent | (none) | category, required on free text |
| Delivery reports | url + method, per message | a workspace webhook; JSON POST only, see below |
| Round-trip context | your own store, keyed by UUID | metadata: arbitrary JSON, echoed on every event |
| Filterable labels | (none) | tags: {name, value} pairs |
| Safe retries | (none documented) | Idempotency-Key header |
| Media | media_urls | no equivalent: media_urls is rejected |
Porting notes:
- A Powerpack UUID becomes a plain sender value. Plivo resolves the number pool, sticky sender and local presence behind the UUID. Bird takes the sender itself in from, so choose it per send, or use a template send, which selects a valid sender for the destination and rejects from.
- type has no counterpart because the endpoint carries it. Plivo selects the channel per request; Bird's SMS, WhatsApp and other channels are separate endpoints. A codebase that switches type at runtime splits into calls to different endpoints.
- Nothing on the Message API corresponds to category. Decide per message type whether it is transactional, marketing, authentication, or service. Authentication traffic in particular should be labelled as such rather than left in a marketing default.
- Review retry semantics separately. Plivo's send reference documents no idempotency key or deduplication mechanism, so a timeout there leaves you guessing. Send the Idempotency-Key header from the first port to reduce duplicate request risk within the three-hour replay window; it is not an exactly-once delivery guarantee.
Carry over opt-outs
Plivo's DND service blocks outbound messages from one Plivo number to one destination once that destination replies with an opt-out keyword. A blocked send comes back flagged with Plivo error code 200, which is one of their message error codes and not an HTTP status, however much it looks like one. That pairing is how a Bird suppression works too: one sender and one subscriber, so the imported scope must cover every sender and programme included in the person’s request.
One thing expands, and it is the reason to count before you import. Inside a US 10DLC campaign, Plivo treats an opt-out from any one number as an opt-out from every number linked to that campaign. Bird stores pairs, so a subscriber who opted out of a four-number campaign becomes four suppressions rather than one. Work out how many pairs your list becomes before you start, because it decides whether the import is a loop of tens or of thousands.
Getting the list out is a console export rather than an API call: filter the numbers in the Plivo console, select them, and use Export CSV from the Choose Action menu. Import the result through the suppression loop. Reading and managing suppressions carries the command, and the reason a manual suppression blocks every category including transactional.
Bird handles supported stop keywords through its country-specific catalog. A send to a suppressed pair is refused at admission with E12077 SMSRecipientSuppressed. A carrier-reported opt-out is a separate recipient_opted_out delivery result. Replace handling for Plivo error code 200 with the appropriate admission and delivery paths, and recreate any custom responses as keyword rules.
That matters again later, once traffic is flowing. Reasons stack rather than merge: a pair you imported as manual that then texts STOP gets a second record with reason keyword_stop, and messages stay stopped until every record for that pair has ended. So resuming a subscriber you once imported means removing both, and a resume that clears only the keyword record looks successful and changes nothing.
Translate delivery statuses
Use this table to compare lifecycle concepts, not to rename events mechanically. Bird chooses a failure event from the reported status and reason. A refused API request creates no message; a rejection after acceptance can produce sms.rejected, including a carrier rejection. Missing delivery evidence remains unknown. Preserve the raw provider status and code alongside your normalized outcome.
| Outcome | Plivo message_state | Bird |
|---|---|---|
| API accepted the message | queued | sms.accepted |
| Handed to the carrier | sent | sms.sent |
| Carrier confirmed delivery | delivered | sms.delivered |
| Carrier reported non-delivery | undelivered | sms.undelivered |
| Permanent failure | failed | sms.failed |
| Refused before sending | rejected | sms.rejected |
| Validity window elapsed | (none) | sms.expired |
Two mechanics change with the names:
- Endpoints replace per-message callback URLs. Plivo takes a url on each send, so the destination is chosen by whoever writes the call. Bird delivers to endpoints your workspace registers, each subscribed to the event types it wants, so a new consumer is a new subscription rather than a change at every call site.
- Signed JSON posts replace a GET callback, if that is what you chose. Plivo's method selects GET or POST for the delivery report; Bird POSTs a JSON event and offers no GET. If you set method=GET, your handler reads the outcome out of query-string parameters, and that handler is a rewrite rather than a re-registration. The same is true one guide over, on the Connectivity Platform path.
- One signature scheme replaces three headers. Plivo signs callbacks with X-Plivo-Signature-V2, X-Plivo-Signature-Ma-V2 and X-Plivo-Signature-V2-Nonce. Bird sends JSON signed per Standard Webhooks, so the verifier is replaced rather than adjusted: swap it for the recipe in Webhooks & events.
Register the endpoint once, naming the event types your handler wants: the sms.* events above are the list to subscribe to, and there is no wildcard that stands in for them. Create an endpoint has the command and the one thing to get right on the first call, which is storing the signing secret the response shows exactly once.
Plivo's numeric error_code values have no one-to-one map. Bird reports a failure with a standardized error code such as invalid_destination, content_rejected, provider_unavailable, or recipient_opted_out; the full list is on the events page. Map your alerting to those.
Cut over
Destinations, senders, and the traffic ramp are provider-independent and covered in the main guide. Two Plivo-specific items belong on the cutover plan.
Your 10DLC brand and campaign are registered with The Campaign Registry through Plivo and do not automatically become Bird registrations. Confirm the applicable migration or registration procedure before submitting paid work. The chain is shorter here. Plivo registers a profile first and then a brand against it, under /v1/Account/{auth_id}/10dlc/; Bird has no profile object, so the business details Plivo holds on the profile are supplied on the brand itself. Start from Register for 10DLC: it covers what each field means, the entity types the registry recognizes, and the requirements call that tells you what to supply before you create the brand, which is the chargeable step.
Numbers you own at Plivo need a port that support arranges, on its own schedule rather than yours. Start it early and it runs alongside the code change.
Next steps
-
Compare Bird and Plivo for SMS: product evaluation and migration considerations
-
Sending SMS: the payload you are porting to, in full
-
Opt-outs and keywords: keyword coverage per country and suppression management
-
SMS events: the event vocabulary your callback handler moves to
-
Webhooks & events: endpoint setup and Standard Webhooks verification
Related resources
Continue with the documentation, guides and examples for this topic. Resources are in English.