Skip to main content

Migrate consent registers and opt-out lists to Orbit

When you move messaging traffic to Orbit, your legal records move with it — the opt-out lists you accumulated, the per-channel consent you collected, and the sender registrations you hold with carriers. This runbook walks that migration end to end: inventory what you have, map it onto Orbit’s endpoints, import in the right order, and prove the fences are up before the first send. The Twilio migration guide covers numbers, messaging services, TwiML, and SDK ports; its wizard imports phone_numbers, messaging_services, templates, and contactsnot consent records. The contact import guide covers audience rows. This guide is the compliance layer both of those migrations need: consent and suppression travel as a separate, deliberate step, and send gates only enforce what you actually import. All endpoints below are rooted at https://api.orbit.devotel.io/api/v1/compliance.
Importing consent or suppression into Orbit asserts that the records are authentic — that the opt-ins were really collected and the opt-outs really happened. Migrating is a transfer of existing records, not a way to manufacture consent. Bring the proof (timestamps, forms, screenshots) with you; this page is not legal advice.

Consent state is the one part of a legacy platform you can never regenerate. If a subscriber told your Twilio number STOP, that fact exists only in Twilio’s records — and when you cut traffic over, Twilio is no longer in the path to enforce it. On Orbit, an address only stays fenced because its record was carried across and imported. Likewise, an opt-in you collected through a web form is the proof that lets you message that contact at all; leave it behind and your reachable audience silently shrinks, and your consent coverage with it. STOP stays STOP. Suppression in Orbit is a hard send gate: a suppressed address is dropped before dispatch regardless of campaign, contact import, or send path. A legacy opt-out that survives the migration keeps fencing the address the day you go live; a legacy opt-out lost in the migration turns a person who said STOP into a recipient of your first campaign — with per-message statutory penalty exposure, and carrier-reputation damage that throttles everything you send from the new numbers. Migrate the suppression list first, and treat it as the minimum legal floor, not a nice-to-have.

2. Inventory: what to export from the legacy platform

Build the inventory before you touch an import. For each source platform you are leaving, list:
  • Opt-out / suppression lists — every address that chose not to receive messages: STOP replies, unsubscribe events, complaints. You want the address value (E.164 phone, email, or WhatsApp ID) plus the channel or channels it applies to.
  • Per-channel consent records — who opted in, on which channel, when, and the supporting evidence: the consent text version they agreed to and proof (screenshots or signed forms) where you hold it.
  • Registration metadata — for the US: 10DLC brand and campaign IDs; for alphanumeric-sender countries: sender-ID registrations (for example Germany’s UCDR, or Saudi Arabia’s SMSA). Registrations do not transfer — you re-register on Orbit — but the legacy record tells you which brands and templates you need to re-create.
The export lever differs by platform — a Twilio opt-out list is a CSV export from console; Iterable and Braze unsubscribe states come out via their REST APIs or scheduled exports. Whatever the lever, normalize each export to a CSV at the end of this step — the import surfaces in step 3 all accept CSV.

3. Map fields to Orbit endpoints

Three surfaces carry your compliance data into Orbit. Pick per source record which one it belongs to:

Suppression import (bulk CSV)

Map your legacy export onto the suppression CSV format: one address per row (phone, email, and/or wa_id columns), an optional reason, and an optional channel override. Header aliases are case-insensitive. Map your legacy fields onto the POST /compliance/consent request: Migration-itself metadata is worth carrying too: put your legacy platform’s export date or batch id on metadata so an audit can trace which import run produced each record.

4. Decide the channel scope

Consent and suppression are scoped per channel in Orbit — an address can be opted in for email and opted out of SMS. Pick the scope deliberately per record type; sloppy scope either over-suppresses (a suppression that blocks channels the user never opted out of) or under-suppresses (an opt-out that misses voice entirely). Use the narrowest correct scope, and be consistent about it for a given source system — a platform that expressed consent per channel (Braze) should map channel-by-channel, not collapse into all. When in doubt for a legacy SMS opt-out, accept the default all: a person who said STOP on SMS does not want to receive your calls either.

5. Sequence the migration

Order matters — consent records land on the contact they resolve to, and a suppressed address must be fenced before a campaign ever points at it.
  1. Import contacts (first) — so identifier values in the consent records resolve to real contacts.
  2. Import suppression lists — run the bulk CSV import next; dry-run first with dry_run=true, then commit.
  3. Record consent opt-ins — loop the Consent API over your opt-in export. Writes update four synchronized surfaces (audit table, contact mirror, suppression list on opt-out, the in-flight STOP fence).
  4. Set up your send gates — turn on the gates your traffic relies on (quiet hours, DNC, frequency caps). Rates and gates are never imported from the legacy platform.
  5. Zero messages until these two verifications pass. Send no campaign until the fences are proven (step 7).

6. Port registrations: 10DLC and sender IDs

Registrations are carrier-surrogate, not tenant data — nothing about a 10DLC brand or an alphanumeric sender ID moves between platforms in the opt-out import. You re-register on Orbit:
  • US traffic: re-register your brand and campaigns on 10DLC registration. Carrier throughput and filtering follow the new registrations, so start this on migration day — 10DLC approval lead time is the usual blocker on US go-live. If you hit a rejection, see 10DLC rejections and re-vetting.
  • Alphanumeric sender IDs: re-register each country on sender-ID registration, upload the KYC documents Orbit asks for, and watch approval status country by country. Some destinations block unregistered alphanumeric senders outright — check the country matrix.
While a 10DLC campaign is pending, route the traffic through a registered number — do not send unregistered US A2P traffic.

7. Validate before go-live

Prove the fences work — do not trust the import counts alone.
  1. Spot-check suppressed numbers. Query the suppression export with status=active and grep for five addresses you know opted out — one per source system. Each should appear with the expected channel scope.
  2. Gate-check a filtered address. Send a test message to a suppressed recipient in sandbox — it should fail synchronously with RECIPIENT_OPTED_OUT. (See Verify a suppression took effect.)
  3. Preview the audience. Run a small preview audience for a campaign — the quiet-hours preview and the campaign preflight report the suppressed cohort explicitly. A large suppressed cohort after import usually means the consent mapping was wrong, not that the fences are too aggressive.
  4. Read compliance-health. Compliance Health Scores rolls consent coverage, opt-out velocity, and STOP-reply rate into a 0–100 score per number and campaign — a falling score in the first week is your earliest warning that a gate is missing a source you did not migrate.
  5. Stop and reconcile on a mismatch. If the imported count and the gated count disagree, do not launch — diff on a suppressed address before you proceed.

8. Common pitfalls

  • Re-import merge semantics. Contact re-import with merge behaves by the consent columns in the new file: a re-import can overwrite consent state only when the new file’s consent column carries it. A “no” in the file’s opt-in column on re-import replaces the prior value; an empty column leaves the existing consent untouched. Never re-import a stale export — its consent columns reflect a point in time and might overwrite newer opt-ins.
  • Rollback removes consent records with the import. Rolling back a contact import also deletes the consent records the import wrote — the cascade is the safety net against orphaned opt-in flags. Re-import the consent after a rollback; the rollback was of data, not of your compliance obligations.
  • Importing opt-ins without suppression first. Sequencing the lists backwards can briefly mark a STOP-recipient as reachable — then a send gate has to catch a campaign in flight. Suppression first, then opt-ins.
  • Treating channel scope as a global default. A bulk CSV import defaults phone rows to all and email rows to email — but a Consent-API call without a channel array scopes nothing. Blanket all is right for a STOP-by-phone; it is wrong for a per-channel Braze export. Check the scope in the import response (by_channel) and the export.
  • Assuming the wizard carried consent. The Twilio import wizard handles numbers, messaging services, templates, and contacts — it does not read Twilio’s STOP list or consent records. Consent is always a separate, deliberate import.
  • Row-splitting a dry-run against the real import. Preview with dry_run=true and commit with dry_run=false on the same file — switching files between preview and commit defeats the validation.
  • Reading legal penalties off a per-import response. The import response counts rows; the statute counts messages. Import is cheap, enforced-at-send-time is the legal boundary — run the validation in step 7 before the first send.

Worked example: Twilio opt-out CSV

A Twilio console export of opted-out numbers arrives as:
Map it onto the suppression CSV — phone goes in any of the accepted header aliases, opt-out date is not an import field but fits in reason:
Import with a national-format default for the un-normalized row:
Flip dry_run to false to commit when the preview’s counts add up. Phone rows all land under scope all — the voice and dialer gates honour them too. An Iterable or Braze export of channel opt-ins is per-contact, per- channel. Script the loop over the Consent API:
A per-channel opt-out rows through the same endpoint with opt_in: false — the Consent API writes the suppression entry for you.