> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Proof-of-Deletion Certificate Runbook

> Issue, read, verify, and present the signed proof-of-deletion certificate for a completed GDPR Art. 17 / CCPA erasure, including the trigger path, failure handling, and regulator presentation.

# Proof-of-Deletion Certificate Runbook

When a right-to-be-forgotten request completes, Orbit can issue a
signed **proof-of-deletion certificate**: the receipt a data subject
or supervisory authority accepts as evidence that the erasure ran
end-to-end. This runbook walks a compliance owner through the full
operational sequence: triggering the erasure, watching it finalize,
issuing the certificate, reading and verifying it, and handling the
case where an erasure did not complete cleanly.

The normative reference for every endpoint used here is
[Data Subject Access Requests](/compliance/dsar). This page is the
step-by-step runbook around the certificate itself.

<Warning>
  This runbook describes Orbit's platform controls. It is **not legal
  advice.** Whether a certificate discharges your obligations in a
  given proceeding depends on your jurisdiction and your role as
  controller or processor. Confirm with qualified counsel.
</Warning>

All endpoints below are rooted at
`https://api.orbit.devotel.io/api/v1/compliance`.

***

## 1. Why a proof of deletion matters

Article 17 obliges you to erase personal data when the right
applies, and Article 12 obliges you to demonstrate to the data
subject that you did. In practice the demonstration is two hops:

1. **Orbit (your processor) shows you the erasure.** The erasure
   executor hard-deletes the subject's rows across every subsystem,
   anchors the deletion in the tamper-evident audit chain, and flips
   the request to `executed`.
2. **You (the controller) show the certificate onward.** The
   certificate packages that execution into a dated, signed,
   per-subsystem statement you can file in your privacy register and
   hand to the data subject or supervisory authority.

Without the second hop, your evidence is "the row is gone," which a
reviewer cannot inspect without access to your database. The
certificate is the legible closure artifact: it names what was
purged, when, under which request, and against which audit anchor,
and it carries a detached signature so the artifact you present
later is provably the one Orbit issued.

***

## 2. Trigger path

An erasure enters the queue through any of the three DSAR intake
paths, all of which feed the same pipeline:

* **Operator-filed** — `POST /compliance/dsar` with
  `request_type: "delete"`, then the identity decision
  `POST /compliance/dsar/{id}/verification` with
  `{ "decision": "verified" }` (delete requests are
  higher-assurance and wait for this decision).
* **Public self-service portal** — the subject files `delete`
  through the two-factor OTP flow; the queued request arrives
  pre-verified.
* **WhatsApp** — a contact tapping **Delete my data** in a
  conversation maps to `request_type: "delete"` automatically.

Once approved, the request's erasure leg is tracked as its own
resource with a cooling-off window (7 days by default) so a
mistaken or disputed deletion can be cancelled while still
`pending`:

```bash theme={null}
# Watch the org-wide erasure list
curl -X GET "https://api.orbit.devotel.io/api/v1/compliance/dsar/erasure-requests?status=pending" \
  -H "X-API-Key: dv_live_sk_..."

# Cancel a pending erasure while inside cooling-off
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/dsar/erasure-requests/gdpr_4e2a…/cancel \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Subject withdrew the request by email." }'
```

### Transfer and finalize sequence

After cooling-off elapses, the executor claims the request and runs
the transfer:

`pending` → `executing` → `executed`

with terminal branches `cancelled` (operator cancel during
`pending`) and `failed` (see
[section 6](#6-when-the-erasure-did-not-complete-cleanly)). The
executor ticks every five minutes; every subsystem delete (messages,
conversations, call logs and recordings, CDP events, consent
records, suppression and DNC rows, voiceprints, knowledge-base
documents, vector memory, and the contact row itself) commits in a
single transaction, then the executor writes the audit-chain anchor
and stamps `executed_at`. **Poll the list until the row reads
`executed`**:

```bash theme={null}
curl -X GET "https://api.orbit.devotel.io/api/v1/compliance/dsar/erasure-requests?status=executed&limit=50" \
  -H "X-API-Key: dv_live_sk_..."
```

Only a row in `executed` can be certified. Anything earlier returns
`409` with code `ERASURE_NOT_EXECUTED`, which is your signal that the
runbook is at the wrong step, not that issuance is broken.

***

## 3. Issue the certificate

<Note>
  **RBAC** — every erasure endpoint in this runbook, including
  certificate issuance, requires an **owner or admin** API key.
  Member-keyed calls are rejected before they reach the compliance
  data.
</Note>

Issuance is a read, not a mutation:
`GET /compliance/dsar/erasure-requests/{id}/certificate` derives the
certificate on demand from the immutable executed row and its audit
anchor, and signs it with a detached HMAC-SHA256 tag. Nothing is
persisted, so issuing twice never creates contradictory artifacts
(see [section 8](#8-retention-and-erasure-of-the-certificate-itself)).

```bash theme={null}
curl -X GET https://api.orbit.devotel.io/api/v1/compliance/dsar/erasure-requests/gdpr_4e2a…/certificate \
  -H "X-API-Key: dv_live_sk_..."
```

Expected result, `200 OK`:

```json theme={null}
{
  "certificate": {
    "certificate_version": "1.1",
    "certificate_id": "erc_8f2c1a9d3b7e5f0a4c2d1e6b",
    "issuer": "Devotel Orbit CPaaS",
    "regulation": [
      "GDPR Article 17 (Right to erasure)",
      "CCPA §1798.105 / CPRA (Right to delete)"
    ],
    "statement": "This certifies that all personal data associated with the identified data subject has been permanently erased from Devotel Orbit CPaaS production systems and propagated as a suppression to downstream destinations, in fulfilment of the data subject's right to erasure. Integrity of this certificate can be verified against the detached HMAC-SHA256 signature.",
    "organization_id": "org_4cf2…",
    "tenant": "tenant_4cf2",
    "erasure_request_id": "gdpr_4e2a…",
    "data_subject_contact_id": "cnt_9b77…",
    "requested_by_user_id": "user_31cd…",
    "requested_at": "2026-08-30T09:12:44.000Z",
    "executed_at": "2026-09-06T09:12:44.000Z",
    "issued_at": "2026-09-18T10:04:31.000Z",
    "attempt_count": 1,
    "audit_log_id": "audit_e86d…",
    "records_erased": {
      "total": 43,
      "by_resource": {
        "contacts": 1,
        "messages": 21,
        "calls": 14,
        "conversations": 7
      }
    },
    "destination_propagation": {
      "total": 2,
      "dispatched": 2,
      "failed": 0,
      "skipped": 0,
      "destinations": [
        {
          "destination": "hubspot",
          "outcome": "dispatched",
          "mode": "delete",
          "action": "delete_contact",
          "lookup_key": "jordan@example.com",
          "error_code": null,
          "propagated_at": "2026-09-06T09:13:51.000Z"
        },
        {
          "destination": "salesforce",
          "outcome": "dispatched",
          "mode": "suppress",
          "action": "suppress_record",
          "lookup_key": "jordan@example.com",
          "error_code": null,
          "propagated_at": "2026-09-06T09:13:52.000Z"
        }
      ]
    }
  },
  "signature": {
    "algorithm": "HMAC-SHA256",
    "key_id": "devotel-orbit-erasure-v1",
    "value": "uYq4w1…base64url"
  }
}
```

Save the response body verbatim. That JSON, untouched, is the
artifact.

***

## 4. Reading the artifact

| Field | How to read it |
| - | - |
| `certificate_id` | Deterministic: derived from the request id and `executed_at`. Re-issuing the same executed request always yields the same id, so citations in correspondence stay stable. |
| `regulation` | The rights this execution evidences. Cite it verbatim when the counterpart asks which right was fulfilled. |
| `requested_at` / `executed_at` | The statutory clock and its closure. The gap between them is your response time; cooling-off is part of it by design. |
| `attempt_count` | How many execution attempts the executor needed. 1 is the norm; a higher number means an earlier attempt hit a transient fault and retried (section 6). |
| `records_erased.total` | Confirmed deleted-row total across subsystems. `null` means every purge ran but no count could be confirmed, not that nothing was deleted. |
| `records_erased.by_resource` | Per-subsystem tallies: messages, conversations, calls, recordings, CDP events, consent records, suppression and DNC rows, voiceprints, and the contact row itself, among others. A number (including 0) is a confirmed count; `null` means that subsystem's table does not exist on your tenant, so there was nothing to purge there. |
| `audit_log_id` | The tamper-evident audit-chain anchor this certificate resolves back to. This is the handle for independent verification (section 5). |
| `destination_propagation` | The external leg: one entry per downstream CRM or marketing destination the erasure was fanned out to, with dispatched / failed / skipped tallies. `total: 0` is a signed, honest "no external destinations were notified," not silence. |
| `signature` | Detached HMAC-SHA256 over the canonicalised `certificate` object. `key_id` names the platform signing key generation. |

***

## 5. Verifying the certificate

Two independent checks, in increasing order of strength:

**Audit-log cross-reference (yours to run).** The certificate's
`audit_log_id` is a row in your organization's audit chain with
action `gdpr.erasure_executed`; its `resource_counts` must match the
certificate's `records_erased.by_resource`, and its timestamps must
match `executed_at`. Every issuance also appends a
`compliance.dsar_erasure_certificate_issued` row naming the
`certificate_id`, so your audit export shows who issued which
certificate and when. Pull the chain through your usual audit
export ([audit export](/compliance/audit-export),
[audit log guide](/guides/audit-log)) and compare. A certificate
whose anchor is missing or whose counts disagree with the chain
should be treated as altered or mis-cited.

**Signature recompute (held by the signing key holder).** The tag
is a self-signature by Devotel Orbit: the HMAC key is the Orbit
platform signing key, so a third party cannot recompute it
independently, exactly as with any processor-attested receipt. The
recipe is published so the computation is transparent and so the
platform can confirm a presented certificate in a regulatory
exchange:

1. Canonicalise the `certificate` object with RFC 8785 (JCS) style
   rules: object keys sorted ascending, no insignificant
   whitespace.
2. Compute HMAC-SHA256 over the string
   `orbit.dsar.erasure-certificate.v1.<canonical-json>`.
3. Base64url-encode and compare with `signature.value` using a
   timing-safe equality check.

The domain-separation context
(`orbit.dsar.erasure-certificate.v1`) is mixed into every tag, so a
valid certificate signature can never double as a valid tag on any
other Orbit-signed surface. A mismatched or malformed tag verifies
false; verification never fails open.

***

## 6. When the erasure did not complete cleanly

Read failures against the actual lifecycle. There is **no**
separate partial-completion status: a request is `pending`,
`executing`, `executed`, `cancelled`, or `failed`, and exceptions on
an *executed* erasure surface as fields on the certificate, not as a
status.

**Nothing half-deletes.** Every subsystem delete for one contact
commits in a single transaction: either all of them commit or none
do. A crash or fault midway rolls the whole erasure back and the
executor retries on a later tick, up to three attempts. If the
request parks in `failed`, the list's `failure_reason` names why:
a faulting execution, an interrupted execution whose retries were
exhausted, or a deletion that committed but whose audit-chain
anchor could not be written. A `failed` request **cannot be
certified**;
issuance returns `409 ERASURE_NOT_EXECUTED`. Escalate a parked
request inside your statutory clock: the 30-day GDPR window keeps
running while the row waits.

**Exceptions on an executed certificate.** Because the transaction
is all-or-nothing, an executed erasure never has silently skipped
subsystem deletes, but it can carry honest flags:

* A `null` count in `records_erased.by_resource` means that
  subsystem's table does not exist on your tenant. Nothing existed
  to purge; the certificate says so rather than pretending a count.
* The external-store purge counters — spillover event blobs and
  encrypted call-recording blobs that could not be confirmed on
  that pass, or a null flag on the vector-memory, knowledge-base,
  or billing-metadata wipe — name stores Orbit could not yet
  attest. Orbit retries these on subsequent passes without
  rolling back the committed deletion; a non-zero failed counter
  is your cue to re-check the list before handing the certificate
  outward.
* `destination_propagation.failed` or `skipped` entries mean a
  downstream destination rejected or was not configured for the
  fan-out. Re-run propagation per
  [CDP erasure propagation](/guides/cdp-erasure-propagation), then
  issue the certificate again: the new artifact keeps the same
  `certificate_id` and folds the updated trail under a fresh
  `issued_at` and signature.

When a counterpart asks "did it really all go?", the answer is the
certificate plus its audit anchor: the certificate states the
outcome, the anchor independently records it, and the two agree by
construction.

***

## 7. Presenting the certificate

In a regulator or customer conversation, present three things
together:

1. **The certificate JSON**, verbatim, as issued. Note the
   `regulation` entries that match the counterpart's claim (GDPR
   Art. 17, CCPA §1798.105 / CPRA).
2. **The audit-chain export** for `audit_log_id`, showing the
   `gdpr.erasure_executed` anchor and its per-resource counts.
3. **Your cover note** as controller: who requested, when, which
   verification path proven identity, and the erasure window. The
   certificate's `requested_at`, `requested_by_user_id`, and
   `attempt_count` supply the facts; your privacy register supplies
   the legal basis.

Keep the posture honest: the certificate is a tenant-presented,
platform-signed attestation. It is strong evidence that Orbit
executed the erasure it records; whether that evidence discharges
the obligation in your proceeding remains a determination for your
counsel, consistent with Orbit's tenant-owned compliance posture.

***

## 8. Retention and erasure of the certificate itself

The certificate is **not persisted**: there is no certificate store
to retain, rotate, or delete, and no stale-copy hazard on the
platform side. Re-issuing at any time reproduces the same
`certificate_id` for the same execution, so citations never dangle.

What ages is the anchor, not the certificate. The erasure request
row and the audit chain follow your organization's retention
posture ([data retention policy](/compliance/data-retention-policy)).
While the anchor is retained you can re-issue and cross-reference
freely; after your retention window closes the anchor ages out and
the cross-reference path in section 5 narrows to the signature
check. The practical rule: issue and file the certificate into
your privacy register while the anchor is live, and treat the
filed copy as your record. Your own filed copies are yours to
retain or erase like any other privacy-register artifact,
including erasing them if a later DSAR demands the closure of the
underlying request's paperwork.

***

## Boundary: DSAR erasure vs CDP reverse propagation

DSAR erasure deletes what Orbit holds for the subject inside your
tenant: the contact row, every linked subsystem, and the derived
stores (recordings blobs, CDP spillover, vector memory). CDP
reverse propagation is the separate, downstream leg that fans the
same deletion out as delete or suppression calls to the CRM and
marketing destinations you activated; its per-destination results
are folded into the certificate's `destination_propagation`
section so one signed artifact attests both legs. The propagation
leg, its destinations, and its retry model are documented in
[CDP erasure propagation](/guides/cdp-erasure-propagation); this
runbook covers issuing and presenting the artifact that closes
both legs.

***

## Related

* [Data Subject Access Requests (DSAR)](/compliance/dsar) — the
  normative reference for intake, verification, and the erasure
  endpoints.
* [CDP erasure propagation](/guides/cdp-erasure-propagation) — the
  downstream fan-out whose outcomes fold into the certificate.
* [Audit export](/compliance/audit-export) — pulling the chain your
  verification cross-reference reads from.
* [Assembling a GDPR Posture End to End](/compliance/gdpr-posture-guide) —
  where erasure closure sits in the full sequence.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.