Preference center: tenant controls and contract
The preference center is a hosted public page where a contact manages their own channel opt-ins, subscription topics, message frequency, and (optionally) files a data-deletion request. This page is the posture and contract reference: the knobs your organization owns, the artifact each contact decision writes, and how the public surface fails. For step-by-step configuration, branding, and link placement, use the walkthrough at Preference center: the public opt-in/opt-out page.The preference center is a tenant-owned control: you choose the
channels, topics, and branding, and your organization holds the consent
evidence. Orbit hosts the page and records the artifacts; it does not
set your posture. This page is not legal advice.
1. Where it sits in the control map
The preference center is the contact-written half of your consent posture. Four surfaces work together:
A decision made on the hosted page is not a UI flag: it lands in the same
consent ledger and suppression entries that a STOP keyword or an operator
action writes.
The contract is defined by two route surfaces:
- Admin/API configuration (
apps/api/src/routes/compliance/preference-center.routes.ts):POST /preference-center,GET /preference-center, andPOST /preference-center/link. - Public, unauthenticated (
apps/api/src/routes/compliance/preference-center-public.ts):GET /preferences/:tokenandPUT /preferences/:token, gated by a signed token instead of a login.
https://api.orbit.devotel.io/api/v1/compliance.
2. The toggle surface
Configure withPOST /preference-center and read back with
GET /preference-center. Every field is validated server-side; a
rejected POST returns 422 with per-field issues. Fields you omit take
the server-side defaults below, so a minimal config is safe, and optional
fields left unset simply do not render.
Topics. Each topic
id is a stable slug
([a-z0-9][a-z0-9_-]{0,63}), unique across the array (duplicates are a
422); name is the page label; description is optional context shown
beside the toggle; defaultOptIn (default false) decides how a contact
with no recorded choice is treated; archived removes the topic from the
page while its history stays in the consent ledger.
Links. A minted link carries an HMAC-SHA256 token
(v1.<payload>.<signature>) scoped to exactly one contact in exactly one
organization, valid for 30 days.
3. What each action writes
Every contact decision on the page writes the same artifacts the STOP-keyword path writes on the inbound webhooks, so a form opt-out blocks sends exactly as a keyword STOP does:
The operator-facing read surface for the same consent ledger is
Consent management
(
apps/api/src/routes/compliance/consent-management.controller.ts), and
the suppression writes mirror the inbound STOP-keyword handler, so both
entry points block sends the same way. The hosted page also shows the contact their own
history, newest first, drawn from that same ledger.
Channel wins over topic. A channel-level opt-out silences every
message on that channel; a topic toggle only narrows consent within the
channels the contact still accepts. Re-opt-in is symmetric only when
every channel in the request is set to
opted_in.4. Regional anchoring
The preference center is the mechanism; the obligations are yours, per jurisdiction.- US CAN-SPAM. A clear unsubscribe mechanism that keeps working for at least 30 days after the message was sent, with requests honored within 10 business days. The 30-day token TTL plus the durable suppression entry cover the mechanism; the honor window is your process. See CAN-SPAM.
- Canada CASL. Unsubscribe requests handled without delay and no later than 10 business days after receipt. See CASL.
- GDPR. Withdrawal of consent must be as easy as granting it (Art 7(3)), information must be transparent (Arts 12–14), and consent must be demonstrable (Art 5(2)). The append-only consent ledger and the contact’s self-serve history are the record-keeping half. See GDPR posture.
5. Failure modes
Error responses carry the code, a human-readable message, and a request
id. The invalid- and expired-token responses do not disclose which
contact or organization a token referred to.
6. RBAC posture
- Changing the config (
POST /preference-center) and minting links (POST /preference-center/link): organization owner or admin roles only. - Reading the config (
GET /preference-center): any authenticated member of the organization. - The public page (
GET/PUT /preferences/:token): unauthenticated by design. The signed token is the authorization: one contact, one organization, 30 days. Email and phone come back masked, so the page never echoes the raw identifiers it was minted for.
Worked example
Configure the center with topics and a custom opt-out label, then mint a per-contact link:201 with the saved config echoed. Then mint the link:
link: the hosted page URL with ?token=v1…
appended. The page itself reads and writes through
GET/PUT /preferences/:token. Placement guidance (email footer, SMS
fallback) lives in the
walkthrough; how the resulting
suppression entries interact with imports and keyword STOPs lives in
Opt-Out & Suppression Lists.
Related
- Preference center: the public opt-in/opt-out page — the setup walkthrough.
- Opt-Out & Suppression Lists — the suppression entries this surface writes.
- Consent management — the operator-side API over the same consent ledger.
- Send gates and pre-send guards — where the STOP-fence and suppression entries are enforced.
- DSAR reference — the tracked erasure pipeline for deletion requests.