Data Subject Access Requests (DSAR)
A Data Subject Access Request (also called a privacy request or consumer rights request) is the formal mechanism a person uses to exercise their rights over the personal data you hold about them — the right to access, delete, correct, port, or opt out of the sale of that data. Most privacy laws give you a hard deadline to respond (30 days under GDPR, 45 under CCPA/CPRA). Orbit gives you three intake paths and one fulfilment pipeline:- Operator-filed DSAR — your support or compliance team files a request on a customer’s behalf through the authenticated API or dashboard.
- Public self-service portal — the data subject files their own request through a public, unauthenticated flow that proves their identity with a two-factor email + SMS OTP before anything is queued.
- WhatsApp user data requests — when a WhatsApp user taps Request my data or Delete my data in a conversation with your business, Meta forwards the request to your WABA and Orbit enqueues it into your DSAR queue — see the WhatsApp section below.
https://api.orbit.devotel.io/api/v1/compliance.
Supported jurisdictions and deadlines
Theapplicable_jurisdiction on a request controls which statutory
clock Orbit’s SLA tracker applies. Operators can reclassify a request
after intake.
Request types
request_type describes what the subject is asking for. The full
CCPA/CPRA verb set is available to operators; the public portal
exposes a friendlier subset that maps onto it.
For CCPA access requests you may also attach
consumer_categories — the CCPA §1798.100(b) categories the subject
is asking about: identifiers, customer_records,
protected_classifications, commercial, biometric,
internet_activity, geolocation, sensory, professional,
education, inferences, sensitive_pi.
Restriction of processing (GDPR Art 18)
The verb set above intentionally has no Art 18 verb. GDPR Article 18 — the right to have processing of your data restricted rather than erased — is valid, for example when the subject disputes accuracy, challenges legality, or needs data kept pending a legal claim. Because restriction means “keep the data but pause processing” — it does not map toknow, delete,
correct, or portability, and there is no hidden request_type
value for it.
When an Art 18 claim arrives, file it under the nearest existing
verb and annotate the legal basis in requester_statement:
correct— Art 18(1)(a) disputes accuracy; rectification is the remedy the subject usually wants anyway.limit_sensitive_pi— the CCPA/CPRA analog: filing it withapplicable_jurisdiction: "cpra"narrows processing in a lawful, tracked way until counsel resolves the claim.- Outside the DSAR pipeline — treat the claim as a freeze through your internal legal-hold process instead of queueing it here at all.
requester_statement so the audit chain and the
GET /compliance/dsar queue explain why the request is open.
A worked example — the subject disputes the accuracy of their
call-activity records and asks you to stop acting on them while
counsel reviews. File the accuracy half as correct (GDPR), and
narrow processing in parallel as limit_sensitive_pi (the CCPA/CPRA
analog) so the restriction is enforced rather than merely noted:
Deciding whether an Art 18 claim is valid — and which of the four
Art 18(1) conditions applies — is a legal determination that stays
with your counsel. Orbit gives you the
request_type verb
set, the requester_statement marker, and the SLA tracker; the
judgement to accept, reject, or release the restriction is yours
at the tenant level.Operator-filed requests
Create a request
POST /compliance/dsar — requires an admin or owner API key.
Provide at least one subject identifier (contact_id,
subject_email, or subject_phone) plus the requester_email that
should receive correspondence.
202 Accepted:
Note —applicable_jurisdictiononly defaults togdprfor rights that exist under GDPR. Theopt_out_saleandlimit_sensitive_pirequest types are CCPA/CPRA-only and have no GDPR equivalent, so you must setapplicable_jurisdictiontoccpaorcpraexplicitly for them. Omitting it (or leaving thegdprdefault) is rejected with422 VALIDATION_ERROR.
Status lifecycle
A request moves through:received → processing → completed
with terminal branches failed, expired, and cancelled. The
verification sub-state is tracked independently:
pending → verified (worker proceeds) or rejected (worker
halts). GDPR/admin-filed rows default to not_required.
Verify or reject identity
Higher-assurance requests (delete, opt-out, limit-sensitive) require an operator decision before fulfilment proceeds:decision is verified or rejected; notes is optional
(≤ 2048 chars). Returns the new verification_status and
verified_at.
Cancel a request
POST /compliance/dsar/{id}/cancel withdraws an in-flight request
(GDPR Art 7(3)). Only works while the request is received or
processing; a terminal request returns 409 Conflict.
List and read requests
GET /compliance/dsar— paginated list. Query:page(≥ 1),page_size(≤ 100, default 25), and an optionalstatusfilter.GET /compliance/dsar/{id}— fetch one request. The response includes the signedexport_url(and itsexport_expires_at) once an access/portability export has been produced, plustables_exporteddescribing the per-table row counts.
Erasure requests
GDPR Art 17 erasures are tracked as their own resource so you can audit and intervene before data is destroyed:GET /compliance/dsar/erasure-requests— list. Query:status(pending,cancelled,executing,executed,failed) andlimit(≤ 500).POST /compliance/dsar/erasure-requests/{id}/cancel— cancel a pending erasure before it executes. Optionalreason(≤ 500 chars). Returns409if it is already executing or done.
Proof-of-deletion certificate
Once an erasure request landsexecuted, you can issue a signed
proof-of-deletion certificate — the receipt a data subject or
supervisory authority can hold as evidence the request was fulfilled.
The certificate is built deterministically from the immutable executed
request row and its audit-chain anchor, self-signed with a detached
HMAC-SHA256 tag. It is not persisted — it is derived on demand, so
re-issuing for the same request and issued_at timestamp always
produces a byte-identical, re-verifiable artifact.
For the full operational sequence around this artifact — trigger
and finalize, reading and verifying the digest, presenting it as
Art. 17 evidence, and handling erasures that did not complete
cleanly — see the
Proof-of-Deletion Certificate Runbook.
Issue the certificate
GET /compliance/dsar/erasure-requests/{id}/certificate — owner or
admin API key. Only executed erasures can be certified; anything
earlier returns 409 Conflict.
Re-verify a certificate
To confirm the artifact has not been altered:- Canonicalise the
certificateobject with RFC 8785 (JCS) — the builder uses the same canonical form other Orbit signed artifacts use: object keys sorted ascending, no insignificant whitespace. - Compute HMAC-SHA256 with the platform signing secret over the string
orbit.dsar.erasure-certificate.v1.<canonical-json>. - Base64url-encode and compare against
signature.valuewith a timing-safe equality check (e.g.crypto.timingSafeEqualin Node).
orbit.dsar.erasure-certificate.v1) is mixed into every tag, so a
certificate signature can never collide with any other
platform-signed surface reusing the same secret. A mismatched or
malformed tag verifies false; it never fails open.
What the certificate is — and is not
The certificate is a tenant-issued artifact: it is the signed receipt you hand to a data subject or supervisory authority to evidence that an erasure executed. Whether that evidence discharges a legal obligation stays with your counsel, consistent with Orbit’s tenant-owned compliance posture. The platform signs integrity of the artifact; the regulatory judgement is yours.Propagation fold
When you run downstream erasure propagation (see CDP erasure propagation), the per-destination outcomes are folded into the certificate’sdestination_propagation section — total, dispatched, failed, and
skipped counts plus one entry per destination. Destinations are sorted
by name and deduplicated (latest outcome wins) so re-issue stays
byte-stable. An empty section is signed as total: 0 — an honest,
verifiable “no external destinations were notified,” not silence.
SLA dashboard
GET /compliance/dsar/sla returns a combined export + erasure SLA
snapshot so you never miss a statutory deadline:
slaDays / 30 ratio, so a request always turns amber
and red at the same fraction of its own deadline. escalation_due flips
5 days before the statutory deadline (slaDays − 5).
For GDPR (sla_days: 30): green (< 20 days elapsed),
amber (20–25), red (26–30), red + breach (> 30);
escalation_due at day 25.
For CCPA/CPRA (sla_days: 45) the same ratios give green
(< 30), amber (30–38), red (39–45), red + breach (> 45);
escalation_due at day 40. Always read the tier boundaries against the
sla_days returned for that request, not the fixed 20/25/30 numbers.
Public self-service portal
The public flow lets a data subject file a request without an account. Identity is proven with a two-factor OTP — an email code and an SMS code — before any request is queued. The endpoints live under/compliance/public/dsar and are unauthenticated, but are
defended by Cloudflare Turnstile, per-IP and per-identifier
rate limits, and a privacy-preserving response shape that never
reveals whether an email/phone pair matches a real contact.
SMS verification codes are delivered through the Devotel softswitch
(the platform’s sole outbound SMS path). They are platform OTPs, not
tenant-billable traffic, and carry no delivery-receipt persistence.
Flow overview
1
Begin
POST /compliance/public/dsar/begin with email, phone (E.164),
request_type (access | delete | portability | opt_out),
and a Cloudflare turnstile_token (required in production).
Returns an opaque claim_id, email_sent: true, and
expires_in: 600. An email OTP is sent immediately.2
Verify email
POST /compliance/public/dsar/verify-email with claim_id and the
6-digit code. Returns state email_verified and next step
phone_send. Codes expire after 10 minutes; max 3
attempts. POST …/resend-email (with claim_id + email) issues
a new code, subject to a 60-second cooldown.3
Send phone code
POST /compliance/public/dsar/send-phone with claim_id and the
phone that matches the one given at begin. Sends an SMS OTP
(expires_in: 600). A 60-second cooldown applies between sends; a
too-soon retry returns 429 with Retry-After.4
Verify phone
POST /compliance/public/dsar/verify-phone with claim_id and the
6-digit code. Returns state phone_verified and next step
submit.5
Submit
POST /compliance/public/dsar/submit with claim_id. Persists an
audit row and — only if the verified email + phone match a contact
in your tenant — queues a real DSAR (pre-marked
verification_status: verified, since the OTP already proved
identity). Returns a reference_id (e.g. dsar_pub_…) and a
queued boolean.Configure the identity-proof senders
The two OTPs are sent from platform-level senders you configure once in your API environment. Set them before publishing the portal — an unset SMS sender with no fallback makes the phone step fail closed.
If
DEVOTEL_DSAR_PROOF_SMS_FROM and DEVOTEL_PLATFORM_DEFAULT_FROM
are both unset, the send-phone step fails closed with a 503 — the
portal returns a “temporarily unavailable” message and the failure is
emitted under the dsar.proof.sms_send_failed metric so it surfaces in
your dashboards rather than silently skipping the second factor.
Likewise, the email step returns 503 when DEVOTEL_RESEND_API_KEY is
unset. Configure both senders before you link the portal publicly.
Abuse defenses
The response shape is identical whether or not the identifiers match a
real contact — the portal never confirms or denies that someone is in
your database. When Redis is unavailable the rate-limit gates
fail open to preserve availability.
Enabling Turnstile protection
The Turnstile gate is configured with two environment variables.Hosting the portal link
Publish the public portal under your privacy policy as the “Submit a privacy request” link. Because the flow self-verifies via OTP, requests that arrive through it are already identity-proven — they land in your operator queue ready to fulfil, and appear inGET /compliance/dsar alongside operator-filed requests.
Inbound DSAR triggers from WhatsApp (Meta user data requests)
Beyond the two paths you file through directly, a third intake path requires nothing from you to switch on. WhatsApp gives every user a Request my data and a Delete my data action inside a conversation with a business. When one of your WhatsApp contacts taps either (the GDPR Article 15 and 17 rights, exercised against you as the data controller), Meta forwards the request to the webhook your WhatsApp Business Account already delivers to Orbit, and Orbit routes it into your DSAR queue so the usual fulfilment pipeline can run.How Orbit routes these requests
- Orbit identifies your organisation by the WABA the webhook arrived on — shared WABAs resolve to every organisation attached to them.
- If the user’s phone number matches a contact in your tenant, that
contact’s
dsar_statusis tagged and the Meta payload is attached to the contact record. - A
dsar_requestsrow is queued in statusreceived, withrequester_statementnoting the request came in from WhatsApp along with Meta’s originalrequest_idwhere present. - From there nothing is special-cased:
scheduler-dsar-exportandscheduler-gdpr-erasurepick the row up exactly as they do operator-filed and portal requests, including downstream erasure propagation and the proof-of-deletion certificate.
How Meta verbs map to request_type
data_request asks you to hand over what you hold, so it queues as an
export; data_delete asks you to erase, so it queues as an erasure.
What you see in your DSAR queue
A WhatsApp-originated row appears inGET /compliance/dsar and the
dashboard queue alongside operator-filed and portal requests, with:
request_typeset by the mapping above.subject_phoneset to the user’s WhatsApp number in E.164 — the one identifier Meta supplies.requester_statementdescribing the WhatsApp origin, including the WABA the request arrived on and Meta’srequest_id.verification_status: verified. Because Meta signs these attestations on your behalf upstream, Orbit does not gate them behind a second identity check; it is ameta_dpo_webhook-originated row, and the queue groups it as such. Art 12(6) still leaves it to you to decide when the identity is sufficient — record your basis if you accept the Meta attestation as verification.
dsar_status of export_requested or deletion_requested, plus the
Meta metadata the webhook delivered — request_id, expires_at, and
signed_request_present (whether Meta signed the request blob).
Duplicate deliveries enqueue once
Meta may deliver the samerequest_id more than once. Before Orbit
inserts a queue row, it checks whether a row for that organisation
already carries the same request_id in requester_statement, and
skips the insert on a match — a redelivered webhook never piles a
second row into your queue. The original row keeps the first
requested_at, and with it the SLA clock.
You still own fulfilment
Orbit routes the upstream trigger into your queue; everything from verification onward is your call, exactly as with the other intake paths:- The Article 12 clock starts when Meta delivers the request to
Orbit, not when your team first reviews the queue row. Meta also
sends its own
expires_atin the payload, visible on the contact — budget your review time inside both. - Verify that the matched contact really is the data subject before
you export or erase, and reject the request through the normal
verification flow (
POST /compliance/dsar/{id}/verification) if it is not. - The 30-day GDPR window applies; the SLA dashboard
escalation tiers track these rows like any other
gdprrequest.
Related references
- CDP erasure propagation — the downstream destination fan-out whose per-destination rows fold into the certificate.
- Self-service DSAR portal — the public, two-factor intake flow for operator-free filing.
- Inbound DSAR triggers from WhatsApp — the Meta-forwarded third intake path.
- Assembling a GDPR Posture End to End — where DSAR intake sits in the full sequence.
- Consent Management — record and look up the consent state a DSAR may ask you to honour.
- Opt-Out & Suppression Lists —
how
delete/opt_outoutcomes flow into suppression. - Call Recording Consent — handling recordings referenced by an access request.
- Upload and manage compliance documents — the document library that holds your privacy register artifacts and signed agreements.
- API Reference → Compliance — full request/response schemas (regenerated from the live API).