EA Consent Input Schema
Overview
The EA Consent Input Schema is a simplified, developer-friendly JSON format for submitting consent data to the EA Consent Issuer Service. This schema abstracts away the complexity of W3C Verifiable Credentials, FHIR structures, and JWT wrapping - the service handles all standards compliance automatically.
Design Philosophy: Developers provide the minimal consent information needed; the service generates all IDs, builds the full VC structure, applies FHIR formatting, and signs the credential as a JWT.
Quick Reference
| Section | Required | Description |
|---|---|---|
subject |
Yes | Subject identity information and internal subject ID |
consent |
Yes | Consent decision and policy details |
evidence |
No | Supporting evidence for identity verification |
captured_by |
No | Optional provenance information about who captured the consent |
Complete Example
{
"subject": {
"id": "source-system-internal-user-123",
"email": "john@example.com",
"phone_number": "+1234567890",
"name": "John Doe",
"given_name": "John",
"family_name": "Doe",
"middle_name": "A",
"birthdate": "1990-01-15",
"gender": "male",
"address": {
"street_address": "123 Main St",
"locality": "Anytown",
"region": "CA",
"postal_code": "12345",
"country": "US"
},
"identifier": [
{
"type": "SSN",
"name": "ssn",
"value": "123-45-6789"
},
{
"type": "DrivingLicense",
"name": "driving_license",
"value": "DL1234567890"
}
],
"linkage": [
{
"system": "datavant-health-v3",
"token": "DV:abc123def456ghi789jkl012mno345pqr678stu901vwx234yz"
}
]
},
"consent": {
"agreed": true,
"summary_html": "I agree to the privacy policy and terms of service.",
"details_html": "<p>I agree to the privacy policy and terms of service.</p><p>This includes:</p><ul><li>Personal information collection and processing</li><li>Data sharing with third parties as needed</li><li>Right to access, modify, or delete my data</li></ul>",
"contains_ppn_consent": true,
"policies": [
{
"uri": "https://example.com/policy/patient_treatment"
}
],
"scope_code": "patient_treatment",
"consented_at": "2025-09-15T10:00:12Z"
},
"evidence": [
{
"type": "AuthenticationEvidence",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"verifies": ["email", "phone_number"]
}
],
"captured_by": {
"client_id": "ppn_webapp",
"server": "https://sandbox.authorization.ea.webshield.io",
"source": {
"record_id": "9f2c1c7c-2b52-4dd8-8bb7-3b1f3d2f2f8a",
"system": "AcmeConsentDB",
"record_type": "consent"
}
}
}
Field Specifications
subject Object (Required)
Contains the subject’s identity information. The service will embed this as a snapshot in the credential.
Required Fields
| Field | Type | Description | Example |
|---|---|---|---|
id |
String | Your internal subject ID - will be formatted as URI by service | "user-abc-123" |
OpenID Connect Standard Fields (Optional)
| Field | Type | Format | Description |
|---|---|---|---|
email |
String | User’s email address | |
phone_number |
String | E.164 | Phone number (e.g., +1234567890) |
name |
String | - | Full name |
given_name |
String | - | First name |
family_name |
String | - | Last name |
middle_name |
String | - | Middle name |
nickname |
String | - | Casual name |
preferred_username |
String | - | Preferred username |
gender |
String | - | Gender (free text) |
birthdate |
String | YYYY-MM-DD | Date of birth |
locale |
String | BCP47 | Locale preference (e.g., “en-US”) |
zoneinfo |
String | IANA | Timezone (e.g., “America/New_York”) |
Address Object (Optional)
Nested under subject.address:
| Field | Type | Description |
|---|---|---|
street_address |
String | Street address |
locality |
String | City |
region |
String | State/Province |
postal_code |
String | ZIP/Postal code |
country |
String | Country code (ISO 3166-1) |
Additional Identifiers (Optional)
Additional identifiers (SSN, driver’s license, passport, etc.) can be provided in the identifier array:
"subject": {
"id": "user-123",
"email": "john@example.com",
"identifier": [
{
"type": "SSN",
"name": "ssn",
"value": "123-45-6789"
},
{
"type": "DrivingLicense",
"name": "driving_license",
"value": "DL1234567890"
},
{
"type": "PassportNumber",
"name": "passport",
"value": "P987654321"
}
]
}
Each identifier object contains:
type(String, required): Classification of the identifier (e.g., “SSN”, “DrivingLicense”, “PassportNumber”)name(String, required): Machine-readable name for the identifiervalue(String, required): The actual identifier value
Linkage Tokens (Optional)
Linkage tokens enable cross-organization patient matching using privacy-preserving tokenization services (e.g., Datavant, Milliman). These tokens allow healthcare organizations to link records across datasets without sharing raw PII.
"subject": {
"id": "user-123",
"email": "patient@hospital.example",
"linkage": [
{
"system": "datavant-health-v3",
"token": "DV:abc123def456ghi789jkl012mno345pqr678stu901vwx234yz"
},
{
"system": "milliman-deterministic-v1",
"token": "MMID:patient-12345-67890-abcde-fghij"
}
]
}
Each linkage token object contains:
system(String, required): Token system identifier (e.g., “datavant-health-v3”, “milliman-deterministic-v1”)token(String, required): The actual linkage token value (opaque string from the tokenization service)
Note: Linkage tokens are passed through directly to the EA Person VC without transformation. The system field identifies which tokenization service generated the token.
Note: At least one identity anchor (email or phone_number) must be provided directly in the subject. The client must always explicitly provide the contactable identity - evidence is used for verification and enrichment only, not as an identity source.
consent Object (Required)
Contains the consent decision and policy details.
Required Fields
| Field | Type | Description |
|---|---|---|
agreed |
Boolean | User’s consent decision (true = opted in, false = opted out) |
summary_html |
String | Brief consent statement (one-liner for UI display). May contain basic HTML tags but NO JavaScript. |
details_html |
String | Full consent text (expanded view for audit trail). May contain basic HTML tags but NO JavaScript. |
contains_ppn_consent |
Boolean | Set to true if consent includes PPN (Professional Provider Number). When true, the mapper automatically injects the canonical PPN scope and policy. Set to false for standard consents. Do NOT manually provide PPN scope or policy when set to true. |
Optional Fields
| Field | Type | Description |
|---|---|---|
policies |
Array | Array of policy references (URIs). Supports layered governance (e.g., company policy + network policy). At least one policy required if present. |
scope_code |
String | Code identifying what the consent covers (e.g., “ppn_consent”) |
consented_at |
String (ISO 8601) | When user gave consent (defaults to current time if omitted) |
Important:
summary_htmlis the brief statement shown to the user (e.g., checkbox label)details_htmlis the full consent text for audit/non-repudiation- Both fields may contain basic HTML tags (
<p>,<b>,<i>,<a>,<ul>,<li>, etc.) but MUST NOT contain JavaScript,<script>tags, inline event handlers, or other unsafe content policiesarray enables referencing multiple governance policies simultaneously (e.g., enterprise privacy policy + network consent policy)- The combination of these fields ensures you capture exactly what the user saw and agreed to
Single Policy Example:
"consent": {
"agreed": true,
"summary_html": "I agree to the privacy policy and terms of service.",
"details_html": "<p>I agree to the privacy policy and terms of service.</p><p>This includes:</p><ul><li>Personal information collection and processing</li><li>Data sharing with third parties as needed</li><li>Right to access, modify, or delete my data</li></ul>",
"policies": [
{
"uri": "https://example.com/privacy-policy-v2.1"
}
],
"scope_code": "ppn_consent",
"consented_at": "2025-09-15T10:00:12Z"
}
Multi-Policy Example (Layered Governance):
"consent": {
"agreed": true,
"summary_html": "I consent to share my health data per company and network policies.",
"details_html": "<p>I authorize data sharing according to AcmeHealth privacy policy and PPN consent framework.</p>",
"policies": [
{
"authority": "https://acmehealth.com",
"uri": "https://acmehealth.com/privacy-policy/v2"
},
{
"authority": "https://ep3foundation.org",
"uri": "https://ep3foundation.org/ppn/consent-policy/v1"
}
],
"scope_code": "patient-consent",
"consented_at": "2024-01-15T10:30:00Z"
}
evidence Array (Optional)
Array of evidence objects supporting identity and consent claims. Each piece of evidence verifies one or more subject or consent attributes.
Supported evidence types: AuthenticationEvidence and DocumentVerificationEvidence.
Evidence Routing: The EA Consent schema uses a two-credential architecture (EA Person VC for identity, EA Consent VC for consent). Evidence is automatically routed to the correct credential based on the verifies field content:
- Person Evidence - Routes to EA Person VC:
- Identity fields:
email,phone_number,name,given_name,family_name,middle_name,nickname,preferred_username - Demographics:
address,birthdate,date_of_birth,gender,locale,zoneinfo - Identifiers:
identifier,identifier:ssn,identifier:passport, etc.
- Identity fields:
- Consent Evidence - Routes to EA Consent VC:
- Consent fields:
consent,policies,scope,purposes
- Consent fields:
- Mixed Categories - Returns validation error during mapping:
- Evidence cannot verify both person and consent fields (e.g.,
["email", "consent"]) - Each evidence item must verify fields from only one category
- Evidence cannot verify both person and consent fields (e.g.,
Why This Matters: Identity verification evidence (e.g., OpenID token verifying email) belongs in the Person VC, while consent decision evidence (e.g., signature verifying consent text) belongs in the Consent VC. This separation ensures each credential contains only relevant evidence for its purpose.
Evidence Object Structure
| Field | Type | Required | Description |
|---|---|---|---|
type |
String | Yes | Evidence type: “AuthenticationEvidence” or “DocumentVerificationEvidence” |
verifies |
Array[String] | Yes* | List of field names this evidence verifies. Required unless OpenID token has email_verified or phone_number_verified claims (auto-inferred). Cannot be empty during mapping - must contain at least one field. Cannot mix person and consent fields - see routing rules above. |
| type-specific | Various | Varies | Additional fields based on evidence type |
*Note: The verifies field is technically optional in the input JSON schema (for OpenID token auto-inference), but during VC mapping it must contain at least one field and cannot be empty.
Verifies Array Convention
The verifies array lists which fields this evidence validates and determines evidence routing to Person vs Consent VCs. Use these conventions:
- Person fields (routes to EA Person VC): Use the field name directly (e.g.,
"email","phone_number","name") - Consent fields (routes to EA Consent VC): Use the field name directly (e.g.,
"consent","policies","scope") - Identifiers: Use
identifier:<name>where<name>matches the identifier’snamefield (e.g.,"identifier:ssn","identifier:driving_license") - Mixed categories: NOT allowed - evidence cannot verify both person and consent fields (causes validation error)
Example:
{
"subject": {
"email": "john@example.com",
"phone_number": "+1234567890",
"family_name": "smith",
"identifier": [
{
"type": "DRIVERS_LICENSE",
"name": "drivers_license",
"value": "123-45-6789"
}
]
},
"evidence": [
{
"type": "AuthenticationEvidence",
"id_token": "eyJhbG...",
"verifies": ["email", "phone_number"]
},
{
"type": "DocumentVerificationEvidence",
"document_type": "drivers_license",
"verifies": ["identifier:driving_license", "family_name"]
}
]
}
Currently Supported Evidence Types
Authentication Evidence
{
"type": "AuthenticationEvidence",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"verifies": ["email", "phone_number"],
"issuer": "https://accounts.google.com"
}
Or without ID token (explicit values):
{
"type": "AuthenticationEvidence",
"amr": ["pwd", "otp"],
"acr": "urn:example:mfa",
"auth_time": "2025-01-13T10:30:00Z",
"verifies": ["email"],
"auth_provider_type": "okta",
"issuer": "https://example.okta.com"
}
Fields:
id_token(String, optional): The OIDC ID token (JWT). When provided, authentication metadata is extracted from the token.amr(Array[String], optional): Authentication Methods References per RFC 8176 (e.g.,["pwd", "otp", "mfa"])acr(String, optional): Authentication Context Class Referenceauth_time(String ISO 8601, optional): When the authentication occurredauth_provider_type(String, optional): Type of authentication provider (e.g., “okta”, “auth0”, “enterprise_sso”, “social”)issuer(String, optional): OIDC provider URL. Ifid_tokenis provided and issuer is not, automatically extracted from the token’s “iss” claimverifies(Array[String], optional): List of subject fields this evidence verifies. Ifid_tokenis provided and verifies is not provided or incomplete, the system auto-infers verified fields from the token claims (see Auto-Inference below)timestamp(String ISO 8601, optional): When this evidence was captured
Dual Mode Behavior:
- With
id_token: The system decodes the JWT and extracts authentication metadata (amr, acr, auth_time, issuer) and identity fields (email, phone_number, etc.). Token-extracted values override any explicit values provided. - Without
id_token: Use explicit values for amr, acr, auth_time, etc. This mode is useful when the original ID token is not available (e.g., consent captured during auth flow but VC issued later from stored data).
Auto-Extraction and Verification Behavior:
OpenID Connect is a standard with a known claim structure. The system automatically extracts fields from ID tokens and handles verification based on field type:
1. Identity Anchors (email, phone_number) - Required in Subject + Strict Validation:
- Must be provided in subject: Client must always explicitly provide
emailorphone_numberin the subject - evidence does not provide identity - Auto-added to
verifiesarray: If token containsemail_verified: trueorphone_number_verified: true, these fields are automatically added to the output evidence’sverifiesarray - Token value validates subject: When evidence verifies an identity field, the token value must match the subject value exactly (validation error on mismatch)
- Verification flags:
email_verifiedandphone_number_verifiedflags are extracted from token claims and included in both EA Consent VC and EA Person VC
2. Supplementary Fields (name, given_name, family_name, etc.) - Input Takes Precedence:
- NOT auto-added to
verifiesarray: Name fields from tokens are supplementary data, not automatically trusted - If subject provides value: Input value is kept (client may have better data, e.g., legal name from HR system vs social login name)
- If subject missing value: Automatically extracted from token
- No validation required: Token and input don’t need to match
Auto-Inference Example:
Input with ID token containing email_verified: true:
{
"subject": {
"id": "user-123",
"email": "alice@example.com"
},
"evidence": [{
"type": "AuthenticationEvidence",
"id_token": "eyJ...", // Contains: email="alice@example.com", email_verified=true, given_name="Alice", family_name="Smith"
"verifies": [] // Empty or omitted - system will auto-populate
}]
}
Output evidence will have:
{
"type": ["Evidence", "AuthenticationEvidence"],
"evidenceDocument": "eyJ...",
"verifies": ["email"], // Auto-inferred from email_verified=true
"issuer": "https://accounts.google.com"
}
And both VCs will include:
- EA Consent VC:
email="alice@example.com",email_verified=true,given_name="Alice",family_name="Smith" - EA Person VC: Email in
contact_infoarray, name fields in FHIRHumanNamestructure
Precedence Rules:
- Identity Fields (email, phone_number):
- Input MUST provide value → Required in subject (validation error if missing)
- If evidence verifies field → Token value must match subject (error if mismatch)
- Supplementary Fields (given_name, family_name, name, etc.):
- If input provides value → Input value wins (override)
- If input omits value → Extracted from token
Example - Validation (email in subject must match token):
{
"subject": {
"id": "user-123",
"email": "alice@example.com" // Must match token
},
"evidence": [{
"type": "AuthenticationEvidence",
"id_token": "eyJ...", // Contains email="alice@example.com"
"verifies": ["email"]
}]
}
Example - Verification with supplementary extraction:
{
"subject": {
"id": "user-123",
"email": "alice@example.com" // Required - provided by client
// name fields will be extracted from token
},
"evidence": [{
"type": "AuthenticationEvidence",
"id_token": "eyJ...", // Contains email="alice@example.com", email_verified=true, name fields
"verifies": ["email", "given_name", "family_name"]
}]
}
// Result: email validated against token, email_verified=true extracted, name fields extracted
Example - Client Preference (supplementary fields):
{
"subject": {
"id": "user-123",
"email": "alice@example.com",
"given_name": "Allie" // Client prefers "Allie"
},
"evidence": [{
"type": "AuthenticationEvidence",
"id_token": "eyJ...", // Contains given_name="Alice"
"verifies": ["email", "given_name"]
}]
}
// Result: email validated, given_name="Allie" kept (not overridden)
Example - Mixed Sources:
{
"subject": {
"id": "user-123",
"phone_number": "+1234567890" // From separate verification
},
"evidence": [
{
"type": "AuthenticationEvidence",
"id_token": "eyJ...", // Only has email
"verifies": ["email"]
},
{
"type": "DocumentVerificationEvidence",
"verifies": ["phone_number"]
}
]
}
// Result: email from authentication, phone from document verification
Document Verification Evidence
{
"type": "DocumentVerificationEvidence",
"document_type": "drivers_license",
"verifies": ["name", "birthdate", "identifier:driving_license"],
"verification_service": "https://verify.example.com",
"verification_result": "passed",
"confidence_score": 0.95,
"timestamp": "2025-09-01T10:30:00Z"
}
Fields:
document_type(String, required): Type of document (e.g., “passport”, “drivers_license”, “national_id”)verifies(Array[String], required): List of subject fields this evidence verifiesverification_service(String, optional): URL of verification service usedverification_result(String, optional): Result (e.g., “passed”, “failed”)confidence_score(Number 0-1, optional): Confidence level (0.0 to 1.0)timestamp(String ISO 8601, optional): When verification was performed
captured_by Object (Optional)
Provenance information about who captured the consent. If omitted, values from configuration file will be used.
Fields
| Field | Type | Required | Description |
|---|---|---|---|
client_id |
String | No | ID of the client application that captured consent |
server |
String | No | URL of the authorization server that issued the consent |
source |
Object | No | Issuer-internal reference to the originating consent record (see below) |
Source Object (Optional)
The source object records an issuer-internal reference to the originating consent record. This is used only by the issuer for support, audit, or reconciliation - it is not used by aggregators or verifiers.
| Field | Type | Required | Description |
|---|---|---|---|
record_id |
String | Yes | Opaque, non-guessable identifier for the source record (UUID or random token) |
system |
String | No | Identifier of the issuer’s source system (e.g., internal DB or service name) |
record_type |
String | No | Type of source record (e.g., consent) |
Requirements:
record_idMUST be opaque and non-guessable- Human-meaningful or sequential identifiers MUST NOT be used
- Fields are for issuer traceability only and have no normative meaning for verifiers
Example:
"captured_by": {
"client_id": "mobile_app_v2",
"server": "https://auth.example.com",
"source": {
"record_id": "9f2c1c7c-2b52-4dd8-8bb7-3b1f3d2f2f8a",
"system": "AcmeConsentDB",
"record_type": "consent"
}
}
Note: This information is used to build the ea_provenance section of the credential for reward distribution and assurance tracking.
Validation Rules
Subject Section
- Subject ID required: Must provide
idfield - Identity anchor required: At least one of
emailorphone_numbermust be provided directly in the subject. Evidence is for verification only, not as an identity source. - Email format: If provided, must be valid email format
- Phone format: If provided, must be E.164 format (e.g., +1234567890)
- Birthdate format: If provided, must be YYYY-MM-DD
Consent Section
agreedmust be boolean: Cannot be null or omittedsummary_htmlnon-empty: Must contain the actual consent textpoliciesarray valid: If present, must contain at least one policy with valid URIscope_codenon-empty: Must specify what consent coversconsented_atformat: If provided, must be ISO 8601 datetime
Evidence Section
verifiesarray non-empty during mapping: Each evidence must verify at least one field when building VCs (can be auto-inferred for OpenID tokens)verifiesfields must exist: Field names must match fields insubjectorconsentobjects- No mixed categories: Evidence cannot verify both person and consent fields - causes validation error during mapping
- Evidence type specific: Each type has its own required fields (see evidence types above)
- Routing validation: Evidence is automatically categorized and routed based on
verifiescontent - unknown fields cause errors
Common Patterns
Minimal Consent (Email Only)
{
"subject": {
"id": "user-123",
"email": "user@example.com"
},
"consent": {
"agreed": true,
"summary_html": "I agree to receive marketing emails",
"details_html": "<p>I agree to receive marketing emails.</p>",
"contains_ppn_consent": false,
"policies": [
{
"uri": "https://example.com/marketing-policy"
}
],
"scope_code": "marketing_consent"
},
"evidence": [
{
"type": "AuthenticationEvidence",
"id_token": "eyJhbG...",
"verifies": ["email"]
}
]
}
Opt-Out Consent
{
"subject": {
"id": "user-789",
"email": "user@example.com"
},
"consent": {
"agreed": false,
"summary_html": "I do not wish to receive promotional emails",
"details_html": "<p>I do not wish to receive promotional emails.</p>",
"contains_ppn_consent": false,
"policies": [
{
"uri": "https://example.com/opt-out-policy"
}
],
"scope_code": "marketing_consent",
"consented_at": "2025-09-20T15:30:00Z"
},
"evidence": [
{
"type": "AuthenticationEvidence",
"id_token": "eyJhbG...",
"verifies": ["email"]
}
]
}
Best Practices
1. Always Provide Evidence
While technically optional, always include evidence for verified claims. This increases trust and enables risk-based processing.
2. Link Evidence to Claims
The verifies array in evidence should reference subject fields using the proper convention:
For top-level fields:
"subject": {
"email": "user@example.com", // ← Top-level field
"phone_number": "+1234567890"
},
"evidence": [{
"verifies": ["email", "phone_number"] // ← Direct field names
}]
For identifiers:
"subject": {
"email": "user@example.com",
"identifier": [
{
"type": "SSN",
"name": "ssn", // ← Identifier name
"value": "123-45-6789"
}
]
},
"evidence": [{
"verifies": ["email", "identifier:ssn"] // ← Use identifier:<name> convention
}]
3. Capture Exact Consent Text
Always store the exact text shown to the user in summary_html for non-repudiation and audit purposes.
4. Reference Full Policy
The policies array should reference the complete, versioned policy documents that were in effect when consent was given. Each policy URI should be stable and versioned.
5. Timestamp Accuracy
Provide the consent.consented_at when you know the exact time consent was given (e.g., from a UI interaction). This may differ from when the credential is issued.
6. Store the JWT
Always store the returned consent_vc_jwt - this is the complete, signed, verifiable credential. You can verify it later using the issuer’s public key.