Documentation Index
Fetch the complete documentation index at: https://orbit-docs.devotel.io/llms.txt
Use this file to discover all available pages before exploring further.
Public consent form
Orbit can host a public landing page where recipients enter their email or phone, choose channels, and opt in or out of receiving messages. Submissions write to the same consent surfaces as carrier-side STOP / START keywords —consent_records, channel_preferences, and suppression_list — so the platform’s outbound send gate immediately honours the new state.
The feature is off by default. Each tenant explicitly turns it on under Settings → Compliance → Public consent form. When disabled, the public URL returns 404.
When to use it
- The footer of email / SMS campaigns where you’d otherwise embed a vendor unsubscribe link.
- Sender bios (Instagram, LinkedIn) where recipients ask you to stop messaging.
- Operator-driven opt-in capture, e.g. when transcribing paper-form consent.
- Bulk re-permissioning campaigns after a privacy-policy change.
Enable the form
- Sign in as an owner or admin.
- Go to Settings → Compliance → Public consent form.
- Toggle Enable public consent form on.
- Copy the public URL (under the toggle) — it has the form
https://orbit.devotel.io/<locale>/consent/<tenantId>.
Optional configuration
All fields below are optional. Leave blank to use Orbit defaults. Branding- Business name — shown above the headline.
- Primary color — 3- or 6-digit hex (e.g.
#0ea5e9). Drives the submit button and success badge. - Logo URL — must be
https://(orhttp://). Rendered above the business name.
- Headline — replaces the default “Manage your preferences” (max 240 chars).
- Body — short explanatory paragraph (max 2000 chars).
- Footer — fine print, support contact, link to your privacy policy (max 1000 chars).
422 INVALID_CHANNEL. Leave all 11 channels selected to accept the platform default (email, fax, instagram, line, messenger, push, rcs, sms, viber, voice, whatsapp).
Pre-fill the recipient identifier
Append?contact=<email-or-phone> to the URL to pre-fill the form. The recipient can still edit the value before submitting.
Programmatic submissions
The form is a thin client over a public REST endpoint. Direct integrations (e.g. an embedded preference centre on your own marketing site) can POST against the same handler.Body
| Field | Type | Notes |
|---|---|---|
tenantId | string | The platform UUID, NOT the org id. Visible in the public URL. |
contactIdentifier | string | RFC-5322 email, E.164 phone (+14155551212), or raw WhatsApp ID / Messenger PSID. |
channels | string[] | One or more of: email, fax, instagram, line, messenger, push, rcs, sms, viber, voice, whatsapp. |
optIn | boolean | true = subscribe, false = unsubscribe. |
source | string | Optional free-form tag (≤ 64 chars). Defaults to "public_form". |
Errors
| Code | When |
|---|---|
VALIDATION_ERROR (422) | Body failed Zod validation. |
INVALID_TENANT (422) | Tenant id is unknown OR the public form is disabled. Same shape either way — no existence-enumeration leak. |
INVALID_CHANNEL (422) | None of the submitted channels are in allowed_channels. |
Read the tenant’s public config
For embedding teams that want to render their own UI against the same config the Orbit-hosted form uses:enabled: false and empty branding / copy / channels. Render a 404 — that matches how the Orbit-hosted page reacts.
What the form writes
Every submission writes to the same three surfaces the carrier-sidehandleOptIn / handleOptOut routes touch:
consent_records— one row per submitted channel withconsent_state = 'opted_in'/'opted_out'. The send-side gate at outbound time checks this column.contacts.channel_preferencesJSONB — per-channelopted_outflag flipped,global_opt_outflipped on opt-out, and an entry appended toconsent_historyfor audit continuity.suppression_list— opt-out inserts achannel='all'row that survives soft-delete. Opt-in setsrevoked_at = NOW()on every active row (audit-friendly, NOT a DELETE).
action: consent.public_opt_in / consent.public_opt_out).
Limits and abuse defence
- The POST + GET endpoints are rate-limited per source IP (
RATE_LIMIT_PUBLIC_UNAUTH— 10 requests per minute). - There is no Clerk session or API key on this route.
- Logs capture source IP, user-agent (truncated to 500 chars), and a contact-identifier kind classifier (
email/phone/whatsapp_id). - Suppression and consent state are authoritative in Postgres; the STOP-fence cache is a sub-RTT convenience that fails open.
Disabling the form
Toggle Enable public consent form off. The public URL returns404 from that moment, and the POST handler returns 422 INVALID_TENANT for that tenant — no in-flight submissions can land.
Stored consent records remain — disabling only stops new submissions. Re-enabling restores the previous configuration block (branding, copy, allowed channels) so an accidental toggle doesn’t lose the customisation.