> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate consent registers and opt-out lists to Orbit

> Carry suppression lists, per-channel consent records, and 10DLC/sender-ID registrations from Twilio, Iterable, or Braze to Orbit — import suppression before the first send, map consent fields, and verify the fences are up.

# 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](/guides/migration-from-twilio) covers
numbers, messaging services, TwiML, and SDK ports; its wizard imports
`phone_numbers`, `messaging_services`, `templates`, and `contacts` —
**not consent records**. The [contact import
guide](/guides/import-and-migrate-contacts) 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`.

<Warning>
  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.
</Warning>

***

## 1. Why consent records must migrate

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](#3-map-fields-to-orbit-endpoints) 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:

| Source record                              | Target                                                         | Shape                                                      |
| ------------------------------------------ | -------------------------------------------------------------- | ---------------------------------------------------------- |
| STOP / unsubscribe list (bulk addresses)   | `POST /compliance/suppression-list/import`                     | CSV upload — one row per address                           |
| Per-contact, per-channel opt-in / opt-out  | `POST /compliance/consent`                                     | JSON body — one call per contact or a loop over your file  |
| Contacts whose file carries opt-in columns | Contact import ([step 4](/guides/import-and-migrate-contacts)) | CSV via the import wizard — consent is a column on the row |

### Suppression import (bulk CSV)

Map your legacy export onto the [suppression CSV
format](/compliance/opt-out-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.

### Consent API (per-contact JSON)

Map your legacy fields onto the [`POST /compliance/consent`
request](/compliance/consent-management#recording-consent):

| Legacy field      | `POST /compliance/consent` field  | Notes                                                                                     |
| ----------------- | --------------------------------- | ----------------------------------------------------------------------------------------- |
| Contact address   | `identifier` (or `contact_id`)    | E.164 phone, email, or WhatsApp ID — the type is resolved automatically                   |
| Channel set       | `channels`                        | One or more of `sms`, `voice`, `whatsapp`, `email`, `push`, `rcs`, and the relay channels |
| Opt-in / opt-out  | `opt_in`                          | `true` / `false`                                                                          |
| Capture mechanism | `source`                          | e.g. `web_form`, `import`, `double_opt_in`                                                |
| Purpose category  | `consent_type`                    | `marketing`, `transactional`                                                              |
| GDPR Art 6 basis  | `lawful_basis`                    | `consent`, `contract`, `legal_obligation`, …                                              |
| Notice version    | `consent_text_version`            | Version of the consent text shown at capture                                              |
| Proof             | `consent_proof_url`               | URL of a screenshot or signed document                                                    |
| Record expiry     | `valid_until` / `expires_in_days` | Optional — set one, never both                                                            |

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).

| Situation                                                              | Scope choice                                                                                 |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Phone-address opt-out imported as a CSV row without a `channel` column | Defaults to scope `all` — suppresses every channel reachable on that number, including voice |
| Email-address opt-out, no `channel` column                             | Defaults to `email`                                                                          |
| Per-row `channel` column present                                       | That value overrides the default per row                                                     |
| Consent API call                                                       | Exactly the `channels` array in the body                                                     |
| Contact-import CSV with a per-channel opt-in column                    | The mapped channel(s) only                                                                   |

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](/guides/import-and-migrate-contacts))
   — so `identifier` values in the consent records resolve to real
   contacts.
2. **Import suppression lists** — run the [bulk CSV
   import](/compliance/opt-out-suppression#bulk-csv-import) next;
   dry-run first with `dry_run=true`, then commit.
3. **Record consent opt-ins** — loop the [Consent
   API](/compliance/consent-management#recording-consent) 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](/guides/quiet-hours-configuration), 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](/guides/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](/guides/10dlc-rejections-and-revet).
* **Alphanumeric sender IDs**: re-register each country on
  [sender-ID registration](/compliance/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](/guides/sender-id-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](/compliance/opt-out-suppression#export-the-suppression-list)
   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](/sandbox/magic-numbers) — it
   should fail synchronously with `RECIPIENT_OPTED_OUT`. (See [Verify a
   suppression took
   effect](/compliance/opt-out-suppression#verify-a-suppression-took-effect).)
3. **Preview the audience.** Run a small preview audience for a
   campaign — the [quiet-hours
   preview](/guides/quiet-hours-configuration) 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](/compliance/compliance-health) 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:

```csv theme={null}
phone_number,date_opted_out
+14155550101,2026-05-12
+14155550102,2026-06-09
(415) 555-0103,2026-08-01
```

Map it onto the [suppression CSV](/compliance/opt-out-suppression#csv-format)
— phone goes in any of the accepted header aliases, opt-out date is not
an import field but fits in `reason`:

```csv theme={null}
phone,reason
+14155550101,twilio_export_opted_out_2026-05-12
+14155550102,twilio_export_opted_out_2026-06-09
(415) 555-0103,twilio_export_opted_out_2026-08-01
```

Import with a national-format default for the un-normalized row:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/suppression-list/import \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -F "file=@twilio-optouts.csv" \
  -F "default_country=US" \
  -F "default_reason=migrated_from_twilio" \
  -F "dry_run=true"
```

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.

## Worked example: consent CSV from Iterable or Braze

An Iterable or Braze export of channel opt-ins is per-contact, per-
channel. Script the loop over the Consent API:

| Export field                  | `POST /compliance/consent` field                                                                                                             |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`                       | `identifier`                                                                                                                                 |
| channel the opt-in applies to | `channels: ["email"]` (one call per channel, or one array for all)                                                                           |
| `subscribed` / `unsubscribed` | `opt_in`                                                                                                                                     |
| `signup_date`                 | `metadata.imported_at` (the original `granted_at` is preserved only on the first opt-in you record; a fresh import stamps the migration run) |
| proof URL where available     | `consent_proof_url`                                                                                                                          |

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/consent \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "identifier": "jordan@example.com",
    "channels": ["email"],
    "opt_in": true,
    "source": "import",
    "consent_type": "marketing",
    "lawful_basis": "consent",
    "metadata": { "legacy_platform": "iterable", "export_batch": "2026-09-17" }
  }'
```

A per-channel opt-out rows through the same endpoint with
`opt_in: false` — the Consent API writes the suppression entry for you.

***

## Related references

* [Migrate from Twilio](/guides/migration-from-twilio) — the
  numbers/services/TwiML port this consent pass should follow; its
  wizard covers configuration, not consent.
* [Import and migrate contacts](/guides/import-and-migrate-contacts) —
  the audience step that precedes the consent-mapping one.
* [Consent Management](/compliance/consent-management) — the full
  consent endpoint surface.
* [Opt-Out & Suppression Lists](/compliance/opt-out-suppression) — the
  suppression import/export reference this guide maps onto.
* [10DLC registration](/guides/10dlc-registration) /
  [Sender-ID registration](/compliance/sender-id-registration) —
  re-register carriers and countries.
* [Quiet hours configuration](/guides/quiet-hours-configuration) —
  tenant-owned send gates; the preview endpoint counts the migrated
  cohort.
* [Import contacts end-to-end](/guides/import-contacts-end-to-end) —
  the underlying wizard the contact step uses.
