Skip to content
AutomotiveMCP
Spec§9RFC / v0.1

Consent Portability

How one party proves to another what a person actually consented to, so a system that did not capture consent can verify it before acting on it. Normative, and deliberately domain-neutral.


This section is normative. It is written in domain-neutral terms: it describes parties, subjects and channels rather than dealerships, shoppers and CRMs, because the problem is not specific to retail automotive. Automotive appears only in the examples.

See it work. A live, in-browser demo runs this whole flow with real ES256: a receipt issued by one party, verified by another before it may send, with toggles that break each guarantee so you can watch the check that catches it. The reference server (packages/reference-server) implements the same flow headlessly.

9.1 The problem

An agent acting for a person triggers work across several independent systems. Each of those systems may then contact that person directly, by SMS, email, or a call.

Consent to be contacted was captured once, by one party, at one moment, under one disclosure. Every other party is now about to act on a consent it did not capture, cannot see, and cannot verify. In practice each one either sends anyway, or refuses to send, and both are wrong: the first is a violation, the second breaks the task the person asked for.

§4 R08 Consent & Compliance already defines what a consent record is. This section defines how one travels, and how a party that did not capture it can rely on it.

The obligations attach to real statutes. In the United States alone that includes the TCPA for calls and messages, CAN-SPAM for email, and state privacy law for the underlying data, each with its own definition of what a valid consent looks like. This specification does not restate those requirements and does not determine whether any given consent satisfies them. It defines a transport and a verification procedure so that the question can be answered with evidence instead of an assumption.

9.2 Roles

  • Capturing party. The party that obtained the consent from the subject and holds the underlying record and its evidence.
  • Relying party. A party that intends to contact the subject on the basis of a consent it did not itself capture.
  • Subject. The person the consent is about.

A party is frequently both. A relying party that later captures its own consent becomes a capturing party for that record.

9.3 The consent receipt

A consent receipt is a signed, self-contained assertion about a single consent record, issued by the capturing party, intended to be handed to a relying party.

A receipt MUST be a JSON Web Signature in compact serialization (RFC 7515), signed with an asymmetric algorithm. Implementations MUST support ES256 and MAY support other asymmetric algorithms. none MUST NOT be accepted under any circumstances.

A receipt is a statement about consent. It is not a grant of access, not an authentication credential, and not a substitute for authorization. A relying party that holds a valid receipt still requires its own authorization to act.

9.3.1 Claims

ClaimRequiredMeaning
issMUSTIssuer. The capturing party, as an HTTPS origin.
subMUSTThe contact point the consent is about, normalized per §9.3.2.
iatMUSTWhen the receipt was issued.
expMUSTWhen the receipt expires. See §9.6.
audMUSTThe relying party this receipt was issued to. See §9.8.
jtiMUSTUnique receipt identifier, used for audit and revocation.
cnsnt_idMUSTThe capturing party's identifier for the underlying consent record.
channelMUSTThe channel consented to: sms, email, voice, mail, or push.
purposeMUSTThe purpose consented to: marketing, transactional, service, or informational.
statusMUSTgranted or revoked. A receipt MAY assert revocation.
granted_atMUSTWhen the subject gave consent. Distinct from iat.
methodMUSTHow it was captured: web_form, sms_reply, verbal_recorded, written, checkbox, api, or imported.
disclosure_hashMUSTSHA-256 of the exact disclosure text shown to the subject, per §9.4.
disclosure_uriMUSTWhere that verbatim text can be retrieved.
status_uriMUSTWhere current status can be checked, per §9.6.
evidence_uriSHOULDWhere the capturing party's evidence can be requested. Access MAY require authorization.
expires_atMAYWhen the consent itself expires, as distinct from the receipt.

A receipt MUST assert exactly one channel and one purpose. A subject who consented to marketing SMS and transactional email has two consents, and gets two receipts. Bundling MUST NOT be used to imply consent the subject did not give.

9.3.2 Subject normalization

The subject of a receipt is the contact point itself, not a person identifier. This is deliberate: the relying party is about to send to an address or a number, and that is precisely what needs to be covered.

  • Phone numbers MUST be normalized to E.164.
  • Email addresses MUST be normalized to lowercase, with no display name and no comment syntax.
  • Postal addresses MUST use the structured address shape defined by the core schemas.

A relying party MUST NOT treat a receipt for one contact point as covering another, even where it believes both belong to the same person. Consent attaches to the endpoint that was disclosed.

9.3.3 Example

Payload of a receipt, before signing. This example is automotive; nothing about the format is.

{
  "iss": "https://crm.example-vendor.com",
  "sub": "+15125550143",
  "aud": "https://scheduler.other-vendor.com",
  "iat": 1786032000,
  "exp": 1786035600,
  "jti": "rcpt_01J9F2K8QW",
  "cnsnt_id": "cns_7Yb3",
  "channel": "sms",
  "purpose": "marketing",
  "status": "granted",
  "granted_at": 1785945600,
  "method": "web_form",
  "disclosure_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "disclosure_uri": "https://crm.example-vendor.com/disclosures/sms-marketing/v4",
  "status_uri": "https://crm.example-vendor.com/consent/status/cns_7Yb3",
  "evidence_uri": "https://crm.example-vendor.com/consent/evidence/cns_7Yb3",
  "expires_at": 1817481600
}

9.4 The disclosure is the load-bearing part

What matters in a dispute is not that a flag was set. It is what the subject was shown when they set it.

  • The capturing party MUST retain the verbatim disclosure text presented to the subject, and MUST serve it at disclosure_uri for as long as any receipt referencing it can be relied upon.
  • disclosure_hash MUST be the SHA-256 of that text, UTF-8 encoded, byte for byte as presented.
  • Disclosure text MUST be versioned and immutable. Changing the wording creates a new disclosure at a new URI. Editing text that a receipt already points at MUST NOT occur, because it silently rewrites the record of what a person agreed to.
  • A relying party SHOULD retrieve and retain the disclosure text alongside its audit record, and MUST do so where it intends to rely on the consent for more than a single immediate action.

9.5 Signing and key discovery

  • The capturing party MUST publish its signing keys as a JWK Set at /.well-known/consent-jwks.json on the origin named in iss.
  • The JWK Set MUST be served over HTTPS with application/jwk-set+json, and SHOULD permit cross-origin reads.
  • Receipts MUST carry a kid header identifying the key.
  • Relying parties MUST verify the signature before relying on any claim, MUST reject a receipt whose iss does not match the origin serving the key, and MUST cache key sets no longer than the cache directives on the response.
  • Capturing parties SHOULD rotate signing keys at least annually, and MUST retain retired public keys for as long as receipts signed with them remain unexpired.

9.6 Revocation is the hard part, and checking is mandatory

A signature proves a receipt was issued. It cannot prove the consent still stands. A person who replies STOP has revoked, and every party holding a receipt is now wrong.

  • Receipt exp MUST NOT exceed 24 hours from iat for marketing purpose, and SHOULD NOT exceed 30 days for any other purpose. Receipts are short-lived on purpose: a stale receipt is the failure mode this section exists to prevent.
  • Before contacting a subject for a marketing purpose, a relying party MUST check status_uri and MUST NOT send on a response of revoked, on an expired receipt, or on a failure to reach the endpoint. Absence of a negative answer is not a positive one.
  • For non-marketing purposes a relying party SHOULD check status_uri, and MUST check it where more than 24 hours have passed since iat.
  • A capturing party MUST serve status_uri with at least: current status, the time of the last status change, and the cnsnt_id. It MUST reflect a revocation within one hour of receiving it, and SHOULD do so immediately.
  • The status endpoint MUST NOT return the subject's contact point or any other personal data. It answers a question the caller already knows how to ask.

9.6.1 Revocation propagates upstream

A relying party that receives a revocation directly from the subject, for example a STOP reply to a message it sent, MUST notify the capturing party named in iss and MUST cease its own use immediately. A revocation heard by any party is a revocation, regardless of who captured the original consent. Treating it as local is the single most likely way an implementation of this section causes a violation rather than preventing one.

9.7 What a receipt does not authorize

A relying party MUST NOT treat a receipt as consent for:

  • A different channel. An SMS consent is not an email consent.
  • A different purpose. A transactional consent is not a marketing consent, and this is the most commonly abused distinction in practice.
  • A different contact point, per §9.3.2.
  • A different party, beyond the audience the receipt was issued to, per §9.8.
  • Retention or secondary use of the subject's data. A receipt says the subject agreed to be contacted. It says nothing about storing, enriching, selling, or training on their data.

Where a relying party's obligations under applicable law exceed what a receipt evidences, those obligations govern. This specification defines a floor, not a defense.

9.8 Audience binding

A receipt MUST name its intended relying party in aud, and a relying party MUST reject a receipt whose aud does not identify it.

Without this, a receipt is a bearer document: anyone who obtains one can present it as justification to contact the subject, and a person who consented to hear from one business finds they have consented to hear from a supply chain. Audience binding is what keeps a portable consent from becoming a tradeable one.

A capturing party issues one receipt per relying party. Receipts MUST NOT be forwarded, and a relying party MUST NOT present a receipt it did not receive in its own aud.

9.9 Audit obligations

A relying party that contacts a subject on the basis of a receipt MUST record, and retain for at least the applicable statutory limitations period:

  • the jti and iss of the receipt relied upon
  • the disclosure_hash, and the disclosure text itself where retrieved
  • the result and timestamp of the status_uri check
  • what was sent, to which contact point, and when

The purpose of this record is to answer, years later, the only question that will be asked: on what basis did you contact this person, and what had they been shown. A system that can produce that record in a minute is a system whose operator can settle the question. One that cannot will settle the case instead.

9.10 Privacy requirements

A receipt names a contact point and asserts something about a person's choices, so it is personal data and MUST be handled as such.

  • Receipts MUST be transmitted only over authenticated, encrypted channels.
  • Receipts MUST NOT be logged in full where logs are retained beyond the audit purpose in §9.9, and the sub claim MUST NOT appear in any log, error message, or trace shared with a third party.
  • A receipt MUST NOT contain any personal data beyond the contact point it concerns. Names, other contact points, account identifiers and behavioural data MUST NOT be included.
  • evidence_uri MAY require authorization, and SHOULD, where the evidence includes a recording, a form submission, or anything else identifying the subject beyond the contact point.

9.11 Tools

ToolLevelPurpose
consent.get_receipt2A capturing party issues a receipt for a consent record, bound to a named audience.
consent.verify_receipt1Verify a receipt's signature, expiry, audience, and current status in one call.
consent.report_revocation2A relying party reports a revocation it received directly, per §9.6.1.

These three tools are defined by this section and do not yet appear in /tools.json. That catalog is generated from the published schemas, and the receipt object above is not yet a published schema. It will be, and the tools will appear then. Until it is, treat the claim table in §9.3.1 as the normative definition.

consent.verify_receipt is offered so that a relying party without a JOSE implementation still has a conformant path. A relying party MAY verify locally instead, and SHOULD, since local verification does not disclose to a third party which subjects it is about to contact.

9.12 Conformance

A server claiming conformance with this section MUST:

  1. Issue receipts conforming to §9.3 for every consent record it holds, on request from an authorized relying party.
  2. Publish signing keys per §9.5.
  3. Serve a status endpoint per §9.6, reflecting revocations within one hour.
  4. Retain and serve verbatim disclosure text per §9.4.
  5. Accept revocation reports per §9.6.1 and apply them to the underlying record.

A server that consumes but does not issue receipts MAY claim conformance as a relying party only, in which case requirements 1 through 4 do not apply and §9.6, §9.7, §9.8 and §9.9 do.

9.13 Open questions

Stated rather than hidden, because this section is new and the working group would rather be argued with early:

  • Should a receipt be bindable to a delegated agent as well as a relying party? An agent acting for a subject is not the same as a vendor contacting them, and §12 may need to extend aud.
  • Is a one-hour revocation reflection window fast enough? Immediate is the right answer technically. One hour is what a system integrating a batch CRM can actually meet, and a requirement nobody meets protects nobody.
  • Should the status endpoint support batch queries? A relying party about to send a campaign to fifty thousand subjects will otherwise make fifty thousand requests, and will be tempted to skip the check instead.
  • Selective disclosure. A receipt reveals the contact point to whoever holds it. A verifiable credential format with selective disclosure could avoid that, at a substantial cost in implementation complexity for parties who mostly have a REST API and a CRM.
Something wrong, missing, or impossible to implement in §9?Propose a change →Opens an email with this section and version already filled in. No account needed, and you keep your copyright.