Number Verification v2 Aggregator Guide

This guide is for an aggregator that wants to offer Number Verification v2 (NV2). NV2 uses an Android Digital Credentials / TS.43 flow to obtain an operator token; it is not a standard phone-number JWT Bearer flow.

Keep private keys, Digital Credential responses, TS.43 credentials, operator tokens, client secrets, and access tokens out of browser logs and source control. Use HTTPS for all public aggregator endpoints.
1. What the Aggregator Does

The aggregator is the verifier in the Android Digital Credentials flow. It creates and signs the credential authorization object requested by the phone, receives the resulting TS.43 credential response, validates and decrypts or exchanges it as required by the operator flow, and returns the outer operator token to the API client. The API client then signs its own JWT Bearer assertion and exchanges that assertion at the Telekom token endpoint.

2. Register the Android Verifier

Before calling the Android Digital Credentials API, register the public identity of the aggregator with the responsible Google/Android Digital Credentials onboarding contact. Provide the following values for each environment and verifier domain:

Verifier domain: the public HTTPS domain used as the verifier ID, for example aggregator.example.com.

Certificate SAN: the verifier domain must be present in the Subject Alternative Name (SAN) of the X.509 signing certificate.

Certificate fingerprint: the SHA-256 fingerprint of the signing certificate, supplied as uppercase or lowercase hexadecimal without separators as requested by the onboarding process.

Public endpoint: the HTTPS DCQL endpoint that the client calls to obtain the signed credential request, for example https://aggregator.example.com/api/v1/dcql.

The MBAPI reference deployment is registered as dt.mbapi.de with certificate SHA-256 fingerprint 2515CFE97F86EF1A8DC92466DDA119676B255CD837FCE2AAE55F83F380ED4C00. Use your own domain and certificate values when registering a separate aggregator.
3. Create the Signed DCQL Credential Request

The API client asks the aggregator for a DCQL credential. A minimum request carries a fresh nonce, a unique request ID, the verifier ID, and the TS.43 use case. A phone-number hint and subscription hint are optional.

POST /api/v1/dcql Content-Type: application/json { "id": "aggregator.example.com", "nonce": "<fresh-cryptographic-nonce>", "requestId": "<unique-request-id>", "use_case": { "number-verification/device-phone-number/ts43": {} }, "phoneNumber": "+491701234567", "subscription_hint": 1 }

The aggregator returns a DCQL credential with format dc-authorization+sd-jwt. Its metadata must contain a credential_authorization_jwt. Sign this JWT with the aggregator signing key. Use the matching signing certificate in the JWT x5c header chain so clients can validate it against the registered certificate fingerprint. The authorization JWT should include the verifier issuer, fresh iat/exp values, the request nonce, a unique jti, consent text, and the encryption JWK expected for an encrypted response. The included reference aggregator implements this signed DCQL-request stage and uses a 900-second lifetime.

Override the Consent Message and Privacy Link

These settings do not override or replace the consent text supplied by Android and the carrier. Android presents the carrier's own privacy-policy text and Provider terms (the carrier's terms describing how it may share the phone number with the app). The aggregator's configured consent message and privacy link are additional information, displayed at the bottom of the sheet after Provider terms. They describe the privacy practices of the application/aggregator. Android controls its UI and carrier-provided content; the aggregator can set only this additional message and its link.

Annotated Android Share info consent sheet. Android and the carrier provide the carrier privacy-policy text and Provider terms. The aggregator can add a separate application and aggregator privacy message and link below Provider terms. Consent sheet illustration based on the provided screenshot. Android/carrier text is labeled separately from the aggregator's additional text.

Android and carrier (not configurable by the aggregator): the carrier identity, information to be shared, the carrier privacy-policy statement/link, and Provider terms.

Aggregator (additional content only): the message shown after Provider terms, plus its link and link label. Use these to explain the application/aggregator's privacy information.

The aggregator supplies this additional content through the consent_data claim inside its signed credential_authorization_jwt. The claim must contain a UTF-8 JSON object encoded as Base64URL without padding. It is not plain text and is not a top-level OpenID4VP request property. Serialize and encode the object before signing the authorization JWT.

Consent JSON before Base64URL encoding: { "consent_text": "The application uses your phone number to provide the requested service, as described in its privacy policy.", "policy_link": "https://your-app.example/privacy", "policy_text": "Application privacy policy" }

To customize the reference aggregator, set the following values in its application.yml, or override them using the equivalent Spring Boot environment variables. Configure the readable values; the aggregator performs JSON serialization and Base64URL encoding automatically.

dcql: credential: jwt: consent_data: "The application uses your phone number to provide the requested service, as described in its privacy policy." policy_link: "https://your-app.example/privacy" policy_text: "Application privacy policy" Environment overrides: DCQL_CREDENTIAL_JWT_CONSENT_DATA="Your additional application/aggregator privacy message" DCQL_CREDENTIAL_JWT_POLICY_LINK="https://your-app.example/privacy" DCQL_CREDENTIAL_JWT_POLICY_TEXT="Application privacy policy"

Restart or redeploy the aggregator after changing its configuration and obtain a new DCQL credential. An already-signed authorization JWT retains its original consent values. The returned key-binding JWT's consent_data_hash is a SHA-256 hash of the exact encoded consent_data string; the TS.43 documentation defines this as evidence that the supplied consent message was displayed.

4. Ask the Phone for the Credential

Build an OpenID4VP request using the signed DCQL credential and invoke the Android Digital Credentials API on the subscriber's Android device. The request uses response_type=vp_token and response_mode=dc_api. The carrier and Android Credential Manager return the TS.43 credential in the resulting vp_token response. Preserve the original nonce and request context for validation.

5. Exchange the TS.43 Credential for the Operator Token

Implement a protected /api/v1/ts43/exchange endpoint in your aggregator, then send the complete phone response and the extracted TS.43 credential to it. The aggregator must validate the signed/encrypted response, validate the expected nonce and verifier identity, and decrypt or perform the operator-required exchange. Do not return only an inner temporary token.

POST /api/v1/ts43/exchange Content-Type: application/json { "ts43_dc": "<TS.43 credential from vp_token>", "verifier_id": "aggregator.example.com", "expected_nonce": "<the original nonce>", "response": { "<complete phone credential response>": "..." } }

Return the complete outer operatorToken wrapper. Telekom requires this wrapper because it carries the operator context and its nested temporary token. Returning only temporary_token, an encrypted credential, or a raw inner JWE will be rejected at the token endpoint.

6. Exchange the Operator Token at Telekom

The API client, not the aggregator, now signs a short-lived JWT Bearer assertion using its registered API-client private key. Its sub must be the complete outer operator token prefixed with operatortoken:. The assertion audience must exactly match the selected environment's token endpoint.

POST https://stg.api.telekom.com/token Content-Type: application/x-www-form-urlencoded grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer assertion=<RS256-signed JWT> JWT claims: { "iss": "<NV2 API client ID>", "sub": "operatortoken:<complete outer operatorToken>", "aud": "https://stg.api.telekom.com/token", "iat": "<now>", "exp": "<now plus five minutes>", "jti": "<unique UUID>", "scope": "number-verification:device-phone-number:read number-verification:verify dpv:FraudPreventionAndDetection" }

Obtain and exchange the operator token promptly. The token and all credentials must match the same environment: a staging operator token and staging API-client credentials are used with https://stg.api.telekom.com/token; production uses the matching production values and https://api.telekom.com/token.
7. Call the NV2 API

Use the returned access token with /number-verification/v2/device-phone-number or /number-verification/v2/verify. See Endpoints & API Info for country/environment URLs and Postman Collections for the ready-to-run NV2 request collection.

Coordinate verifier registration, certificate rotation, allowed origins, and production onboarding with the responsible Telekom product and Android Digital Credentials contacts before going live.
An unhandled error has occurred. Reload 🗙