Skip to main content

Deploy the public self-service DSAR intake portal

Devotel Orbit ships a public, unauthenticated intake page where a data subject files a privacy request themselves — access, erasure, portability, or opt-out — and proves their identity with a two-factor email + SMS OTP before anything enters your queue. This guide walks through deciding on, publishing, and operating that portal. For the endpoint surface and jurisdiction tables, read the DSAR reference; for the operator-side walkthrough of the queue portal filings land in, read the DSAR operator guide.
The portal is your intake surface. Orbit hosts it and tracks the statutory clocks, but the legal duty to respond within the deadline stays with you, the controller. Confirm your obligations with counsel.

When the public portal beats operator-filed DSAR

The operator flow (file a request in the console) works perfectly well — until it does not:
  • Volume. Every intake routed through your support or compliance team costs an operator round-trip per request. High-volume consumer businesses get subject requests daily; self-service takes them to zero ops touches.
  • Unauthenticated subjects. An operator-filed request via your authenticated API can only be filed by someone with a session. The public portal gives strangers on the internet a lawful front door.
  • Statutory clocks. GDPR Article 12 gives you 30 days, CCPA/CPRA 45, LGPD 15. An intake channel that forces every request through a human queue burns those days on triage. Portal filings arrive pre-verified, so the clock is spent on fulfilment, not identity paperwork.
If you publish a privacy policy that promises a self-serve channel — and most GDPR-obligated controllers do — the portal is how that promise actually works.

Deploy the portal

Route and hosting model

The portal is a hosted public page on Orbit’s web surface — no account, no API key, no session. Publish it as the “Submit a privacy request” link in your privacy policy, website footer, and in-product settings:
<locale> follows the same locale prefix as every other Orbit web page (for example en, fr, de). The page renders a two-step identifier form behind a Cloudflare Turnstile bot check: the subject enters their email and phone (E.164), chooses a request type, and the identity proof begins.

The two-factor identity proof

Nothing is queued until the subject proves they control both an email address and a phone number:
  1. Begin — the subject submits email + phone + request type. An email OTP is sent immediately.
  2. Verify email — the 6-digit email code (10-minute TTL, 3 attempts; a 60-second resend cooldown).
  3. Send phone code — an SMS OTP to the same phone (60-second cooldown between sends).
  4. Verify phone — the 6-digit SMS code.
  5. Submit — only with both proofs on file does a request enter your queue. The whole claim has a 30-minute end-to-end TTL.
The identity proof runs against platform-level senders you configure in your API environment — an unset SMS sender (with the platform fallback unset too) fails the phone step closed with a 503, and the email step fails closed when the email delivery key is missing. Configure both before you link the portal publicly: In production, the first step also requires a Turnstile token: DEVOTEL_TURNSTILE_SECRET_KEY on the API and NEXT_PUBLIC_DEVOTEL_TURNSTILE_SITE_KEY on the web deployment. Both keys come from the Cloudflare dashboard (Turnstile → Add site). Without the secret the gate is open — set it in production. SMS verification codes go out through the Devotel softswitch as platform OTPs — they are not tenant-billable traffic.

Request types a subject can pick

The public form exposes a friendlier subset that maps onto the operator enum: Operator-only rights (correct, limit-sensitive-PI, non-discrimination) are intentionally not exposed publicly — a subject who needs those can still file through your operators.

Jurisdiction and the SLA clock

The subject picks the jurisdiction their request falls under; that choice sets the statutory clock your queue displays: Operators can reclassify the jurisdiction after intake — so a subject’s mis-selection is fixable inside your queue, not a wrong clock you must live with. When a portal filing targets opt_out, jurisdiction defaults to CCPA/CPRA rather than GDPR, matching the operator-side validation that rejects a GDPR-scoped opt-out request.

Limit the queue to the jurisdictions whose rules you follow

The jurisdiction a request lands under is the single input that decides both the SLA clock it gets and which request_type values it may carry. Keeping the pairing right is worth the few minutes it takes to learn — the DSAR reference documents the applicable_jurisdiction enum and defaults; this section folds those semantics into the runbook you run from.
  • The operator default is gdpr. Create a request without an applicable_jurisdiction and it lands on the GDPR clock (30 days) with the GDPR right in scope. That is correct for EU/EEA subjects and wrong for anyone whose rights come from CCPA/CPRA.
  • Two rights exist only under CCPA/CPRA. A request of type opt_out_sale (opt out of sale/share) or limit_sensitive_pi (limit use of sensitive PI) has no GDPR equivalent, so the API rejects a gdpr-scoped request of either type with 422 VALIDATION_ERROR. Set applicable_jurisdiction expressly to ccpa or cpra on those, never let it take the default.
  • Portal verbs are shorthand for the operator enum. A subject taps one of four friendly labels; your queue runs on the operator’s full vocabulary. access files as know (the CCPA name for the GDPR access right), opt_out files as opt_out_sale, and delete/portability arrive under their own names. Mapping a row back the other way for legal review is a lookup, not a triage.
Walk a CCPA-only request through the pairing: a California consumer asks you to stop selling or sharing their data. An operator files it with both fields aligned:
The cpra jurisdiction anchors a 45-day clock and admits the opt_out_sale type; leave jurisdiction out and the same body fails at the door with 422 VALIDATION_ERROR. The same pairing discipline applies to limit_sensitive_pi — and when a subject files the shorter opt_out through the portal, the intake path defaults jurisdiction to CCPA/CPRA for you (above), precisely because the GDPR default would otherwise be invalid.

Five operator mistakes that burn your SLA clock

Each of these shows up in queue triage; each is a one-word fix if you know where the jurisdiction/request-type semantics live.
  1. Omitting applicable_jurisdiction on a California consumer. The default is gdpr, which starts a 30-day clock. Pass ccpa or cpra when the subject is a California consumer — and pass it on every operator-filed request, so the habit never relies on defaults.
  2. Reaching for a California-only right while gdpr is in scope. opt_out_sale and limit_sensitive_pi exist only under CCPA/CPRA; a gdpr-scoped request of either type is rejected with 422 VALIDATION_ERROR. File the request under ccpa or cpra, set the right, and the error goes away.
  3. Misreading the portal’s shorthand verbs. A subject who taps access has filed know (the CCPA name for the access right); opt_out has filed opt_out_sale. When a legal team asks you to report on the right in question, map the portal verb back to the operator enum before you answer — do not paraphrase.
  4. Filing the short name instead of the operator enum. The API enum is the operator’s vocabulary (know, opt_out_sale, limit_sensitive_pi, …). Send request_type: "opt_out" from your own integration and validation rejects it — the shorthand belongs to the portal surface only.
  5. Trusting the default for the CPRA opt-out. A CPRA opt-out of sale/share filed with no jurisdiction is a GDPR-scoped opt_out_sale — rejected at the door. The explicit pair is one line:

Lifecycle visibility for ops

Portal filings that match a contact in your tenant land in Settings → Compliance → DSAR, in the same list and the same GET /compliance/dsar response as operator-filed requests. Because the OTP already proved identity, they arrive pre-marked verification_status: verified — the export or erasure worker starts immediately and no operator approve/reject gate sits in the way.
  • Erasure-class portal filings (the subject picked “delete”) are pulled into the Erasure requests (Art. 17) tab’s portal section — the same place the console groups erasures from the admin dialog — with their SLA badge beside them.
  • Wrong jurisdiction picked? Reclassify the row from the queue; the SLA badge re-anchors to the corrected window.
  • Need the paper trail? The document-verification and manual-review methods exist for operator-filed rows; a portal row’s proof is its two-factor OTP, recorded in the audit chain (below).

Evidence a portal filing leaves behind

Every stage of the public flow writes to your tamper-evident audit log — the begin attempt, the email verification, the phone verification, and the submit — with hashed (never raw) identifier values, the source IP, and the user agent. That is the record a regulator or your evidence binder pulls from when they ask how a subject’s identity was established. Two consequences to plan around:
  • The breach/incident register and binder count audit rows across both intake paths — portal filings do not need a separate evidential story.
  • When a filing does not match any contact, an audit row is still written (scoped to the platform rather than your tenant) so the forensic trail exists even for a no-match case — while the portal’s response shape still never reveals whether a contact matched.

Integrate the API directly

The portal page is a thin client over a public REST sequence. If you embed your own intake UI (an in-app privacy center, a partner-hosted form), call the same handlers — the response envelope and rate limits are documented in the DSAR reference:
Only the submit step returns 201; every earlier step is a 2xx progression toward it. Whatever the integration, render a Turnstile widget at step 1 — production begin calls without a token are rejected with 422 TURNSTILE_REQUIRED.