> ## 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.

# Consent-Receipt Integrity Binding

> Mint and verify a signed digest that binds a recording-consent receipt to the exact recording artifact it covers, so an auditor can replay-verify provenance.

# Consent-Receipt Integrity Binding

A recording-consent receipt states "the parties on this call consented to
recording". A recording file holds the audio. On their own, neither proves
the two belong together — a stored receipt carries a plain recording
pointer, so audio could be swapped under the same id and the pointer would
still resolve.

Orbit's recording surface already seals each recording with a content
SHA-256 and a per-tenant tamper-evident hash chain (see
[Call Recording Consent](/compliance/recording-consent) for the recording
surface). The consent-receipt integrity binding chains one more link:

```
consent receipt → recording content hash + integrity-chain hash → signature
```

Orbit mints a portable, HMAC-signed digest that binds the receipt's
immutable fields to the bound recording's content hash and (when the
recording is sealed) its integrity-chain hash. Any later edit to the
receipt, a swap of the audio bytes, or a re-pointed recording pointer
invalidates the signature. You can ship the digest alongside an exported
recording so a downstream auditor — finance, healthcare eDiscovery, or
outside counsel — can verify provenance without API access, the same
posture the signed export digest gives recordings.

<Note>
  The binding is **tenant-owned and optional**. Orbit never mandates it;
  you configure the posture you want and call the digest endpoints only if
  you need auditable receipt-to-recording provenance. This page describes
  platform tooling, not legal advice.
</Note>

***

## Endpoints

Two endpoints on the recording-consent API surface handle issuance and
offline verification:

| Operation       | Endpoint                                                     |
| --------------- | ------------------------------------------------------------ |
| Mint a digest   | `POST /api/v1/compliance/recording-consent/integrity`        |
| Verify a digest | `POST /api/v1/compliance/recording-consent/integrity/verify` |

### Mint a digest

Send the call and receipt identifiers; Orbit resolves the receipt rows and
the bound recording's seal, signs the digest, and returns it.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://api.orbit.devotel.io/api/v1/compliance/recording-consent/integrity" \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "call_id": "call_9f2ab1",
      "consent_receipt_id": "rcpt_a1b2"
    }'
  ```
</RequestExample>

The response `data` is the full signed digest object (schema below). A
404 with `RECORDING_CONSENT_RECEIPT_NOT_FOUND` means the receipt id did
not resolve to a live row for this call — capture consent before minting
a digest.

### Verify a digest

Submit the full digest object back; Orbit recomputes the signature over
the signed fields and reports whether it is intact. Verification performs
no writes.

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://api.orbit.devotel.io/api/v1/compliance/recording-consent/integrity/verify" \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "digest": {
        "consent_receipt_id": "rcpt_a1b2",
        "call_id": "call_9f2ab1",
        "recording_id": "rec_7c9d",
        "consent_method": "ivr_announcement",
        "consent_state": "granted",
        "jurisdiction": "us-ca",
        "consent_text_version": "v1",
        "captured_by": "user_84kd",
        "captured_at": "2026-09-27T14:03:11.000Z",
        "revoked": false,
        "recording_content_sha256": "8f…3a",
        "recording_integrity_hash": "c2…9b",
        "binding_status": "bound",
        "issued_at": "2026-09-27T14:05:02.000Z",
        "signature": "0d…e4"
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "verified": true,
      "consent_receipt_id": "rcpt_a1b2",
      "binding_status": "bound"
    },
    "meta": {
      "request_id": "req_j1r9",
      "timestamp": "2026-09-27T14:05:11.000Z"
    }
  }
  ```
</ResponseExample>

`verified: false` tells you the submitted digest was altered after
issuance (or the signature hex was malformed) — treat it as a failed
provenance check, not a transport error.

***

## Digest schema

The signed payload carries every field a verifier needs; the signature is
an HMAC-SHA256 (hex) over the canonicalised field set below (sorted-key
canonicalisation, so key order is irrelevant but the field **set** must
match exactly).

| Field                      | Type              | Meaning                                                                                                      |
| -------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------ |
| `consent_receipt_id`       | string            | Logical receipt id the audit trail groups its rows under.                                                    |
| `call_id`                  | string            | The call the receipt and recording anchor to.                                                                |
| `recording_id`             | string \| null    | Bound recording id, or null when consent predates the artifact.                                              |
| `consent_method`           | string            | How consent was captured (for example `ivr_announcement`).                                                   |
| `consent_state`            | string            | `granted`, or `declined` if any party row declined.                                                          |
| `jurisdiction`             | string \| null    | Jurisdiction tag recorded at capture.                                                                        |
| `consent_text_version`     | string            | Version of the disclosure text presented.                                                                    |
| `captured_by`              | string            | User id (or `system`) that captured the consent.                                                             |
| `captured_at`              | string (ISO-8601) | Earliest capture instant across the receipt's party rows.                                                    |
| `revoked`                  | boolean           | True when any party row has been withdrawn (GDPR Art 7(3)).                                                  |
| `recording_content_sha256` | string \| null    | SHA-256 hex of the recording's media bytes.                                                                  |
| `recording_integrity_hash` | string \| null    | The recording seal's current chain hash, when sealed.                                                        |
| `binding_status`           | enum              | One of the four binding statuses below.                                                                      |
| `issued_at`                | string (ISO-8601) | Instant the digest was issued. Re-issuing the same fields at the same instant reproduces the same signature. |
| `signature`                | string            | HMAC-SHA256 (hex) over the canonicalised fields above.                                                       |

### Binding statuses

| `binding_status`        | Meaning                                                                                                                                                                                                               |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bound`                 | Receipt is bound to a sealed recording — both the content hash and the integrity-chain hash are present.                                                                                                              |
| `bound_unsealed`        | Recording exists with a content hash but no integrity-chain seal yet; the digest still binds the content bytes.                                                                                                       |
| `recording_unavailable` | The receipt points at a recording, but that recording row is missing or tombstoned (for example after GDPR erasure). The receipt audit trail outlives the audio, so this is an expected terminal state, not an error. |
| `unbound_no_recording`  | The receipt carries no recording pointer — consent was collected before the artifact finalised.                                                                                                                       |

<Note>
  `recording_unavailable` and `unbound_no_recording` still produce a
  signed digest: the absence of a recording is part of the attested
  provenance and is itself data you hand to an auditor.
</Note>

***

## Configuring a tenant posture

Per Orbit's tenant-owned compliance model, this binding is an **optional,
tenant-controlled posture** — never a platform mandate. Decide and
document your posture:

* **Off (default posture).** If receipt-to-recording provenance is not a
  requirement, do nothing — receipts and recordings still exist; you
  simply never mint a digest.
* **Evidentiary posture on.** Mint a digest at (or immediately after)
  recording completion for calls flagged by your compliance checklist,
  and store the digest object alongside the exported recording artifact.
  Verification is then a stateless replay: recompute the signature and
  compare, without re-deriving from live rows.

Whatever posture you pick, record it in your internal compliance runbook
so an auditor can see the decision was deliberate. Issuing and verifying
digests is audit-logged under the digest-issuance action.

***

## Privacy-carve risks

Two deliberate design choices shape what the binding does and does not
do:

1. **Nothing new is stored.** The digest is computed deterministically
   from already-persisted receipt rows, the recording seal, and the
   platform signing secret, then returned to you — it is stored nowhere
   on Orbit's side. Re-issuing with the same fields and `issued_at`
   instant reproduces the same signature, so loss of a stored digest is
   recoverable and the signing surface stays minimal.
2. **Receipts outlive recordings.** When a GDPR erasure tombstones the
   recording, the receipt row survives (the audit trail is the point),
   and any digest already issued flips to a well-defined terminal status:
   `recording_unavailable`. Verification of that earlier-issued digest
   still succeeds, because the signature was minted over the fields
   that described the binding at issuance time — but any **new** digest
   you mint after erasure attests `recording_unavailable` rather than
   `bound`.

Keep both facts in mind when you design your evidence binder: store the
digest next to the exported artifact while the artifact exists, and treat
`recording_unavailable` on a fresh issuance as an expected post-erasure
state rather than a tamper signal.
