Skip to main content

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.
You can use all three at once — they are mirrors over the same ledger, so recipients arriving through different inputs still fence identically. Run a CSV backfill first, then let the Consent API and Preference Center keep the ledger current from that point.

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:
Read the rows and the scopes they land on, left to right:
  1. A bare phone with no channel column → scope all (a STOP on a phone number covers every channel reachable on it).
  2. A bare email with an explicit email scope (redundant — email rows default to email; kept here to make the file self-describing).
  3. Phone + email on one row with channel: all → the phone row fences every channel; the email row’s scope stays all too, but an email address only ever gates email sends anyway.
  4. A phone narrowed to sms by the channel column — the recipient stayed reachable on voice and email by design.
The 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 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):
Revoke (also 201 Created — the revoke call is the same endpoint with opt_in: false):
A revocation is single-action and final immediately — a plain grant asserts on the spot, a revocation revokes on the spot; the double-opt-in handshake is the only flow that holds a pending state. Re-granting after a revoke never rewrites history: it appends a new grant row, and the prior revoke is still in the trail. Read back with GET /compliance/consent/history:
The newest item is the revoke (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.
The Preference Center is the recipient-facing input. Configure it once, then mint a per-contact signed link:
The returned 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 (email on a phone row, or a channel column 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.
Rule of thumb: behave like a STOP — write 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:
Each exported row carries the provenance fields that pin the entry point and event back: 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: true pass 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/lookup reads the current state instantly.
For larger audits pair the suppression export with 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.