Guide
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.
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.
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.
{
"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.
curl -X POST "https://mydatahub.emailmovers.com/v1/compliance/receipts/verify" \
-H "Content-Type: application/json" \
--data '{ "jws": "eyJhbGciOiJFUzI1NiIs..." }'{
"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.
validis the signature alone. It says the receipt was signed by a published MyDataHub key, with the receipt type in the header.order_existssays whether the order is on record.licence_in_force_untilis a date only when the order is on record, live, not revoked and not ended. It isnullotherwise, 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.
// 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.
// 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.
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 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 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.
- Verify one now with REST reference: Verify a compliance receipt.
- Look at the trust data with REST reference: Get the trust document.