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 Email 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 identifier
  • value (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.


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_html is the brief statement shown to the user (e.g., checkbox label)
  • details_html is 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
  • policies array 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.
  • Consent Evidence - Routes to EA Consent VC:
    • Consent fields: consent, policies, scope, purposes
  • 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

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’s name field (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 Reference
  • auth_time (String ISO 8601, optional): When the authentication occurred
  • auth_provider_type (String, optional): Type of authentication provider (e.g., “okta”, “auth0”, “enterprise_sso”, “social”)
  • issuer (String, optional): OIDC provider URL. If id_token is provided and issuer is not, automatically extracted from the token’s “iss” claim
  • verifies (Array[String], optional): List of subject fields this evidence verifies. If id_token is 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 email or phone_number in the subject - evidence does not provide identity
  • Auto-added to verifies array: If token contains email_verified: true or phone_number_verified: true, these fields are automatically added to the output evidence’s verifies array
  • 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_verified and phone_number_verified flags 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 verifies array: 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_info array, name fields in FHIR HumanName structure

Precedence Rules:

  1. 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)
  2. 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 verifies
  • verification_service (String, optional): URL of verification service used
  • verification_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_id MUST 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 id field
  • Identity anchor required: At least one of email or phone_number must 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
  • agreed must be boolean: Cannot be null or omitted
  • summary_html non-empty: Must contain the actual consent text
  • policies array valid: If present, must contain at least one policy with valid URI
  • scope_code non-empty: Must specify what consent covers
  • consented_at format: If provided, must be ISO 8601 datetime

Evidence Section

  • verifies array non-empty during mapping: Each evidence must verify at least one field when building VCs (can be auto-inferred for OpenID tokens)
  • verifies fields must exist: Field names must match fields in subject or consent objects
  • 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 verifies content - unknown fields cause errors

Common Patterns

{
  "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"]
    }
  ]
}
{
  "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.

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
}]

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.