# Compliance receipts

> Every order issues a signed receipt that proves what was delivered and how it was screened. Verify its ES256 signature with the published key, or with one request and no account.

Source: https://emailmovers.com/mydatahub/docs/compliance-receipts
Updated: 4 October 2026

Every order issues a compliance receipt: a compact JWS signed with ES256 that counts the records delivered, the suppressions removed, the subscriber classes, the TPS and CTPS screens and the Article 14 status. Anyone can verify it with the published key, or with `POST /v1/compliance/receipts/verify` and no account. One agent can therefore prove to another that a list is licensed and screened.

> **Not yet live.** The public key set is served at `https://mydatahub.emailmovers.com/.well-known/jwks.json` once v2 launches. The local build serves it at `/.well-known/jwks.json` on the API host.

## Get the receipt

`GET /v1/orders/{id}/receipt` and `get_compliance_receipt` return it once the order is ready. A PDF twin is at `mydatahub.emailmovers.com/receipts/{id}`. The response has the compact `jws`, its `alg` and `kid`, the `jwks_url`, and the decoded `claims`.

```json
{
  "id": "rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R",
  "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
  "alg": "ES256",
  "kid": "rcpt-2026-09",
  "jwks_url": "https://mydatahub.emailmovers.com/.well-known/jwks.json",
  "pdf_url": "https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R",
  "claims": {
    "receipt_id": "rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R",
    "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
    "issued_at": "2026-09-26T10:16:04Z",
    "dataset_version": "2026-W39",
    "records_delivered": 5000,
    "removed_by_suppression": 41
  }
}
```

## What the claims say

| Claim | Meaning |
| --- | --- |
| `receipt_id`, `order_id`, `issued_at` | Which receipt, for which order, and when |
| `dataset_version` | The immutable build the records came from |
| `records_delivered`, `removed_by_suppression` | What you received and what was left out |
| `counts.subscriber_class` | Records by `ltd`, `plc`, `llp`, `scottish_partnership`, `public_body`, `charity_incorporated`, `sole_trader`, `ordinary_partnership` and `unknown` |
| `counts.tps_ctps_status` | Records `clear`, `tps_registered`, `ctps_registered`, `tps_or_ctps_registered` or `not_screened` |
| `counts.article14_status` | Records `notified`, `publicly_notified_disproportionate_effort` or `pending_within_window` |
| `lawful_basis`, `lia_ref` | UK GDPR Article 6(1)(f) legitimate interests, and the published assessment |
| `licence` | The licence block: type, `reuse_until`, channels, warranty and terms version |
| `residency_statement_version`, `sub_processor_list_version` | Which versions of those published statements applied |
| `verified_within_days` | The share of delivered emails verified within 30, 60 and 90 days |
| `content_sha256` | SHA-256 of the delivered file, in lower case hex |

## Verify with one request

`POST /v1/compliance/receipts/verify` takes the JWS and needs no account.

```bash
curl -X POST "https://mydatahub.emailmovers.com/v1/compliance/receipts/verify" \
  -H "Content-Type: application/json" \
  --data '{ "jws": "eyJhbGciOiJFUzI1NiIs..." }'
```

```json
{
  "valid": true,
  "order_exists": true,
  "licence_in_force_until": "2027-09-26",
  "revoked": false,
  "receipt_id": "rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R",
  "checked_at": "2026-10-01T09:00:00Z"
}
```

Read the three answers separately.

- **`valid`** is the signature alone. It says the receipt was signed by a published MyDataHub key, with the receipt type in the header.
- **`order_exists`** says whether the order is on record.
- **`licence_in_force_until`** is a date only when the order is on record, live, not revoked and not ended. It is `null` otherwise, so a signature alone never reads as a licence.

A JWS that does not verify returns `valid` false and nothing else. Its claims are never read. This operation is REST only, because the 22 MCP tools are fixed.

## Verify offline

You can check a receipt with no call to us. Fetch the key set, verify the signature, and check the `typ` header, because a quote and a confirmation token are signed by the same key.

A receipt's protected header has `typ` set to `mydatahub.receipt+jws`. Receipts signed before a key rotation still verify, because the key set lists every retired key.

```ts
// verify-receipt.ts
import { compactVerify, createRemoteJWKSet } from "jose";

const jwks = createRemoteJWKSet(new URL("https://mydatahub.emailmovers.com/.well-known/jwks.json"));

export async function verifyReceipt(jws: string): Promise<Record<string, unknown>> {
  const { payload, protectedHeader } = await compactVerify(jws, jwks, { algorithms: ["ES256"] });
  if (protectedHeader.typ !== "mydatahub.receipt+jws") throw new Error("This JWS is not a receipt.");
  return JSON.parse(new TextDecoder().decode(payload)) as Record<string, unknown>;
}
```

This needs the `jose` package. Then check the file you were given against the receipt.

```ts
// check-file.ts
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";

export async function matchesReceipt(path: string, claims: { content_sha256: string }): Promise<boolean> {
  const digest = createHash("sha256").update(await readFile(path)).digest("hex");
  return digest === claims.content_sha256;
}
```

The hash records the file on the day of delivery. A copy downloaded later no longer matches once a record in it was objected to, which blanks that person's details, or once a number's clear TPS and CTPS screen is more than 28 days old, which makes it read `not_screened`. See [Suppressions and objections](https://emailmovers.com/mydatahub/docs/suppressions.md).

## The compliance object on every record

Every preview row, order record, lookup and enrichment carries a `compliance` object, repeated as CSV columns and as the MyDataHub property group on a CRM push.

| Member | Values |
| --- | --- |
| `lawful_basis` | `legitimate_interests_art6_1_f` |
| `subscriber_class` | `ltd`, `plc`, `llp`, `scottish_partnership`, `public_body`, `charity_incorporated`, `sole_trader`, `ordinary_partnership` or `unknown` |
| `email_marketing_basis` | `pecr_corporate_subscriber` or `consent_required` |
| `article14_status` | `notified`, `publicly_notified_disproportionate_effort` or `pending_within_window`, with `article14_notified_at` and `notice_version` |
| `tps_ctps_status` | `clear`, `tps_registered`, `ctps_registered`, `tps_or_ctps_registered` or `not_screened`: the result of the screen on `tps_ctps_screened_at`, valid until `tps_ctps_valid_until`. In a sample, an order's records (each time they are fetched, downloaded or pushed) and an enrichment, a clear number reads `not_screened` once that screen is more than 28 days before the day the record is served |
| `suppression_status` | `none`, `objected_to_emailmovers`, `objected_to_licensee`, `deceased` or `left_company` |
| `provenance` | The source of the company, contact and email, with dates, the verification method and the provider location |

Companies House company data is under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/) and carries its attribution. Officer names are personal data outside that licence, so they carry an Article 14 status instead.

## The buyer kit

Each order returns `buyer_kit` with four signed URLs.

- A draft legitimate interests assessment.
- First-email wording that satisfies Article 14 at first communication.
- An Article 30 stub for your record of processing.
- A due diligence file with the receipt, the validation report and the order's audit trail.

The documents are made in the app, and the links appear once they exist.

## How it answers the ICO

The [ICO's guidance for buyers of broker data](https://ico.org.uk/for-organisations/direct-marketing-and-privacy-and-electronic-communications/organisations-using-marketing-services-of-data-brokers/) says accepting a broker's assurances is not enough, so the evidence is fields and not badges.

| ICO question | Where to look |
| --- | --- |
| Who compiled the data? | `provenance.contact.source`, and the data owner statement on the trust page |
| Where was it obtained from? | The source per field in `provenance`, and the Companies House attribution |
| What privacy information was used? | `article14_status`, `article14_notified_at` and `notice_version` |
| When was it compiled and how old is it? | `compiled_at`, `last_verified_at`, `record_age_days` and `dataset_version` |
| How was it collected? | `provenance.contact.method` and the verification provider and location |
| What consent records exist? | None claimed. The basis is legitimate interests, with `email_marketing_basis` per record |
| Was it screened against opt-out and suppression lists? | `tps_ctps_status` and its screen date, `suppression_status`, `suppressed_removed` per count and the receipt counts |

The trust page is also data: `GET /v1/trust`, `GET /v1/compliance/lia`, the `mydatahub://compliance` resource and `https://mydatahub.emailmovers.com/trust.json`.

## Next steps

- Read how a receipt is made in [Quotes and orders](https://emailmovers.com/mydatahub/docs/quotes-and-orders.md).
- Verify one now with [REST reference: Verify a compliance receipt](https://emailmovers.com/mydatahub/docs/reference/rest/verify-compliance-receipt.md).
- Look at the trust data with [REST reference: Get the trust document](https://emailmovers.com/mydatahub/docs/reference/rest/get-trust.md).
