Choose your suppression entry point
Three inputs write into the one suppression ledger: the bulk CSV import, the Consent API, and the Preference Center. Every send gate reads that single ledger regardless of which input wrote the row, so a suppressed address is fenced identically no matter how the opt-out arrived. This guide teaches the decision between the three inputs — when to pick each one, how to wire it — and the two disciplines that keep the ledger clean: choosing the right channel scope (the one place the three mirrors can disagree at the gate) and reading the ledger back for audit.All three entry points are tenant-owned controls: you decide what
qualifies as an opt-out, and the consequences attach to your own
records. Orbit operates the platform and enforces the gate; the
lawfulness of your sends stays with you. This guide is not legal
advice.
The decision table
How to choose:
- Backfilling a list? CSV import. It deduplicates within the file and against prior imports, and returns per-row acceptance counts.
- Recording consent as it changes? Consent API. It is the only
input that carries the GDPR burden-of-proof fields (
lawful_basis,purpose,consent_text_version, a proof URL) on the consent record. - Letting the recipient decide? Preference Center. The signed link expires in 30 days and scopes the page to exactly one contact.
Worked example: the full CSV row set
POST /compliance/suppression-list/import takes a
multipart/form-data file. The parser reads a header with
case-insensitive, position-independent column names, and accepts any
of phone, email, wa_id (plus an optional channel scope
override and reason) per row:
- A bare phone with no
channelcolumn → scopeall(a STOP on a phone number covers every channel reachable on it). - A bare email with an explicit
emailscope (redundant — email rows default toemail; kept here to make the file self-describing). - Phone + email on one row with
channel: all→ the phone row fences every channel; the email row’s scope staysalltoo, but an email address only ever gates email sends anyway. - A phone narrowed to
smsby thechannelcolumn — the recipient stayed reachable on voice and email by design.
file_sha256 in the response and the run’s per-row verdicts are
what reconcile the import later. A full contract — column aliases,
validation reasons, and every HTTP rejection shape — is on
Opt-Out & Suppression Lists.
The grant/revoke pair through the Consent API
The Consent API is the live path: one write per event, structured consent metadata on the record, effective immediately. This pair records a grant and, two months later, its revocation — and then reads them back the way an auditor does. Grant (201 Created):
201 Created — the revoke call is the same endpoint
with opt_in: false):
GET /compliance/consent/history:
granted: false, a revoked_at), and
the original grant row carries the same revoked_at — grant and
revocation tied by timestamp is the audit answer.
Consent Management covers the full
field list, the lookup endpoint a send gate can call pre-send, and the
expiring-consent sweep.
Preference Center: the signed link
The Preference Center is the recipient-facing input. Configure it once, then mint a per-contact signed link:link carries an HMAC-signed token with a 30-day TTL.
Place it in your email footer’s unsubscribe area, or inline in SMS and
WhatsApp bodies. Every opt-out the contact makes on the page records a
consent revocation and adds the same ledger row as the other two
inputs. Full configuration fields, link placement, and the public
page’s update surface are in the
Preference Center guide;
the endpoints are summarized on
Send Gates.
Scope: decide all vs a single channel first
Scope is the one decision that survives entry-point choice — a row’s
scope is the exact set of channels it fences, and the send gate
honours it precisely. Decide it from the signal the opt-out carries,
not from the transport that delivered it:
The two failure shapes, both diagnosed on
Troubleshooting: suppression scope mismatch:
- Under-blocked — a recipient on the list still receives messages.
The row carries a narrowed scope (
emailon a phone row, or achannelcolumn that trimmed it), so the channel you’re sending on isn’t the one it fences. - Over-blocked — a recipient you meant off one channel can’t be
reached anywhere. The row landed scope
all, which is the phone-row default.
all on phone identifiers
unless you have an explicit reason to narrow. And match scope with
the recipients’ intent, not the sender’s file format.
Reading the ledger back for an audit
The suppression ledger is the single picture every gate reads. Export it to answer a regulator, an auditor, or a discovery request:suppression_id, channel (the scope),
address, status (active | revoked), reason, source,
contact_id (empty for a bulk-imported address with no contact),
plus suppressed_at / revoked_at / created_at. Access is
restricted to owner/admin keys, and every export run itself lands in
the audit log.
Honor-time expectations per entry point:
- CSV import — effective at import-completion: the gate reads
exactly the rows the run accepted; a
dry_run: truepass writes nothing. - Preference Center — effective the moment the contact saves: the page POSTs straight into consent and suppression in the same request.
- Consent API — the record writes immediately, and a short-lived
STOP-fence propagates to in-flight campaign batches within ~10
minutes. Pre-send
GET /compliance/consent/lookupreads the current state instantly.
GET /compliance/consent/export — the consent trail carries the
lawful-basis and proof columns the suppression list does not. Together
they are the evidence binder: the consent row proves the event, the
suppression row proves the fence.
Edge cases
Re-import with a corrected scope. The import is idempotent on(channel, address) — re-uploading the same address replaces its
scope cleanly. Export status=active, filter to the mis-scoped rows,
re-import them with a corrected channel column (or none, to accept
the address-type default). The full fix loop is the troubleshooting
page linked above.
Mirrors never double-remove. Because all three inputs write the
same ledger, a recipient who opts out twice — a STOP keyword and then
the Preference Center, or a CSV row that the Consent API already
recorded — converges to the same fence. Idempotency lives on
(channel, address), so the second write is a no-op rather than a
conflict.
Mirrors can scope-mismatch. The one disagreement the three inputs
can have: a Consent API revoke on a phone identifier asserts all,
but a phone row your CSV narrowed to sms stays narrowed. Pick the
scope that matches the recipient’s intent at the write, and favour
all on phone targets unless narrowing is deliberate.
A revoked row stays in the ledger. Revocation flips status to
revoked and stamps revoked_at — the row is kept for audit, never
deleted. Export with status=revoked to see the history; export
status=all to see both.
Related references
- Opt-Out & Suppression Lists — the full CSV import contract and the export schema this guide’s audit section reads.
- Consent Management — the grant, revoke, lookup, and history endpoints behind the Consent API pair.
- Preference Center guide — every config field, link placement, and the public page’s update surface.
- Send Gates — the quiet-hours, DNC, and other gates that run alongside suppression on the send path.
- Troubleshooting: suppression scope mismatch — diagnosing the under- and over-blocked shapes.
- SMS opt-out & opt-in rules — the inbound keyword side of the ledger.