Sign inGet started

Migrate SMS from Infobip

This page maps Infobip's SMS API, Blocklist, and delivery reports to Bird. Follow the main migration guide in order and use these mappings for steps 3, 4, and 5.
Two differences do most of the work. Infobip's payload is built for the bulk case, so one message to one person is an array of messages, each holding an array of destinations, with the words two levels down in content.text; POST /v1/sms/messages takes from, to and text at the top level. And your Infobip base URL is personalised per account, in the shape xxxxx.api.infobip.com, authenticated with Authorization: App <key>. Bird sends from a regional host with a bearer key, so the host your code holds changes at the same time as the payload shape.

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 Infobip 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/infobip.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 Infobip 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

The rename table is short because the shape change is the work:
What it doesInfobipBird
Recipientmessages[].destinations[].toto (one per request)
Sendermessages[].senderfrom
Bodymessages[].content.texttext
Intent(none)category, required on free text
Delivery reportswebhooks.delivery, per messagea workspace webhook subscribed to the delivery events below
Round-trip contextwebhooks.callbackDatametadata, but see the size note below
Campaign groupingoptions.campaignReferenceIdtags, for filtering only; see below
Flashoptions.flashno equivalent
Validityoptions.validityPeriodno equivalent: validity_period is rejected
Delivery windowoptions.deliveryTimeWindowno equivalent
Safe retries(none in their generated clients)Idempotency-Key header
Porting notes:
  • Three levels become none. The nesting exists to carry many messages and many destinations in one request. Sending one message to one person, Bird takes the three fields at the top level, so the builder that assembles the arrays is deleted rather than translated.
  • callbackData is bigger than metadata. Infobip accepts up to 4000 characters and returns it on the delivery report. Bird's metadata is capped at 2 KB serialized and is echoed on every event for the message, not just the terminal one. The echo is the better deal; the cap is not, so anything approaching the limit has to be trimmed to a key you can look up rather than carried whole.
  • campaignReferenceId is reporting context, not a campaign migration. Bird's tags are {name, value} pairs that become query dimensions, so you can slice analytics by campaign the way you did. What does not come with them is a campaign object: a tag does not create or configure a broadcast. Evaluate the campaign workflow separately when moving audience campaigns.
  • campaignReferenceId is not an idempotency key. Infobip defines it as an ID for tracking a campaign's performance, so it groups but does not deduplicate. If you were relying on it to make a retry safe, you were not covered; the Idempotency-Key header is what does that here.
  • Nothing corresponds to category. Infobip's message options cover validity, delivery window, flash and regional settings, and none of them declares why the message is being sent. Decide per message type whether it is transactional, marketing, authentication, or service.
  • Two option fields have no home. validityPeriod is reserved and answers 422 SMSUnsupportedFeature; deliveryTimeWindow has no counterpart, so scheduling windows move into your own dispatcher.

Carry over opt-outs

Infobip keeps a Blocklist: a list of the recipients who have opted out of your communication, managed through the Blocklist API or through People in the web interface, with a send to anyone on it refused. Keyword triggers add to it automatically, so a subscriber texting STOP lands there without your application doing anything.
That makes the export the easiest of any provider in this set, and the expansion the largest. A Blocklist entry is one subscriber for the whole account; a Bird suppression is one sender-and-subscriber pair. So every entry becomes as many suppressions as you have senders: a thousand-entry Blocklist and six senders is six thousand records. Work out the multiplier before you start, because it is the difference between an import that takes a minute and one that needs batching and a progress log.
Preserve the Blocklist's original scope. Do not narrow a withdrawal during migration just because the new technical model can express narrower pairs. A workspace-wide preference can represent a broader request; it is a separate owner from sender suppressions. Recheck both when deciding eligibility.
Import through the suppression loop. Reading and managing suppressions carries the command, and the reason a manual suppression blocks every category including transactional.
Once you are here, Bird answers the stop keywords itself from its own catalog per country, so the keyword triggers you configured have no counterpart to rebuild, and any custom ones become keyword rules. Reasons stack rather than merge, so a pair you imported as manual that later texts STOP holds two records, and messages stay stopped until both have ended.

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.
Infobip reports a status group and a status name on each delivery report, and Bird emits an event type:
OutcomeInfobip status groupBird
API accepted the messagePENDINGsms.accepted
Handed to the carrierPENDINGsms.sent
Carrier confirmed deliveryDELIVEREDsms.delivered
Carrier reported non-deliveryUNDELIVERABLEsms.undelivered
Permanent failureREJECTEDsms.failed
Refused before sendingREJECTEDsms.rejected
Validity window elapsedEXPIREDsms.expired
EXPIRED is the row to read closely, because it covers two different things on their side and only one of them exists here. Infobip expires a message either when their own platform's validity period runs out, which defaults to 48 hours, or when the operator returns expired as the final status. Bird sets no validity window of its own and runs no timer that ends a message, so sms.expired only ever comes from the carrier's delivery receipt. The operator-reported half maps across; the platform-timer half has no counterpart, and a message that would have expired on their clock stays in flight here until the carrier decides.
REJECTED appears twice on purpose. Infobip uses it both for a message it refused itself and for one the operator returned as rejected, which are Bird's events chosen from the processing or carrier outcome and its reason; a carrier rejection can produce sms.rejected. The status name inside the group is what tells them apart, so a handler that branched only on the group needs the name once it is here. PENDING also covers two rows, because it is the group the message sits in from acceptance until a terminal report arrives.
Three mechanics change with the names:
  • Subscriptions replace per-message webhooks. Infobip names a webhook on each message, so the destination is chosen by whoever writes the call, and the content type is chosen with it. Bird delivers JSON to endpoints your workspace registers, each subscribed to the event types it wants, so a second consumer is a second subscription rather than a change at every call site.
  • You lose the per-message choice, including XML. Infobip lets a message pick JSON or XML and attach up to 4000 characters of callback data. Bird sends JSON only, and callbackData becomes metadata, which is echoed on every event for that message rather than only on the report.
  • Pull becomes push. Infobip lets you fetch reports from a reports endpoint as well as receive them. Bird has no equivalent poll for events; subscribe, and read message state through the API when you need it on demand.
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. Bird sends JSON signed per Standard Webhooks; 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.
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 rather than to Infobip's numeric group and name pairs.

Cut over

Destinations, senders, and the traffic ramp are provider-independent and covered in the main guide. Three Infobip-specific items belong on the cutover plan.
The host moves, and it is configuration rather than code. Your Infobip base URL is issued per account; Bird sends from a regional host chosen when your workspace was created. Find every place that host is set before the cutover, including environment variables, secrets managers and deploy manifests, because a missed one fails at runtime rather than at build.
Your 10DLC brand and campaign are registered with The Campaign Registry through Infobip's number registration API and do not automatically become Bird registrations. Confirm the applicable migration or registration procedure before submitting paid work. 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 Infobip need a port that support arranges, on its own schedule rather than yours.

Next steps

Related resources

Continue with the documentation, guides and examples for this topic. Resources are in English.