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