Call Forwarding Signal

Overview

Additional Information

JWT Authentication Flow Call Forwarding Signal
Unconditional Call Forwarding Information Retrieval
Monitoring
Call Divert
Home Network Check

Overview

Additional Information

Premium Info Home Network Check
MVNO Discovery For Vodafone

Overview

Additional Information

Premium Info MVNO Discovery For Vodafone
Match (CAMARA)

Overview

Additional Information

JWT Authentication Flow Match (CAMARA)
Authentication Flow Match (CAMARA)
Monitoring
Number Lookup For Vodafone

Overview

Additional Information

Premium Info Number Lookup For Vodafone
Number Recycle
Number Recycling Camara

Overview

Additional Information

JWT Authentication Flow Number Recycling CAMARA
Authentication Flow Number Recycling CAMARA
Check Number Recycling
Number Verification 2.0 (CAMARA)
SIM Swap (CAMARA)
Tenure (CAMARA)

Overview

Additional Information

JWT Authentication Flow Tenure (CAMARA)
Authentication Flow Tenure (CAMARA)
Check Subscriber Tenure
Monitoring
NumberVerify (CAMARA)

Number Verification 2

2.1 - Live - Available in: DE, GB, NL

Number Verification 2 is Vodafone’s next-generation authentication mechanism, designed to deliver a seamless, secure, and low-friction user experience.

It leverages operating system-level integration with the operator’s entitlement server and token-based API authentication to verify a user's mobile number, without requiring manual input or SMS delivery.

This API follows the CAMARA standard specification.

JWT Bearer Flow

To verify a mobile number using Number Verification 2, the implementing service must generate a JWT assertion that includes a TS.43-compliant temporary token. This JWT is used in a bearer token request to Vodafone's authentication server.

Flow Summary

  1. The mobile device obtains a TS.43 token via SIM-based authentication.

  2. The app sends this token to its backend.

  3. The backend constructs an encoded and signed JWT with the TS.43 temporary token in the sub claim.

  4. The JWT is sent as part of a bearer token request to Vodafone.

  5. Vodafone validates the JWT and issues an opaque access token.

  6. The access token is used to call the /number-verification/v2 endpoint to retrieve or verify the mobile number.

For more details on the use of JWT Bearer as a means of requesting an OAuth access token, see RFC 7523 JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants.

JWT Header Structure

The following claims should be present in the JWT header:

Claim

Description

kid

the key ID is a hint indicating which key was used to secure the JWT.

typ

the media type of this complete JWT.

alg

the algorithm used to sign the JWT.

Example of JWT header:

{ "kid": "dmp-prod-kid", "typ": "JWT", "alg": "RS256" }

JWT Payload Structure

The JWT must be signed and include the following claims:

Claim

Description

sub

Subject: must contain the operator token retrieved from the TS.43 process and start with the string operatortoken

aud

Audience: OAuth endpoint URL (incl. the path parameter).

scope

Scope value for the API endpoint being called. Supported individual scopes are: "dpv:FraudPreventionAndDetection number-verification:device-phone-number:read" "dpv:FraudPreventionAndDetection number-verification:verify" For requesting both services use: "dpv:FraudPreventionAndDetection number-verification:device-phone-number:read number-verification:verify"

iss

Issuer: identifies the entity issuing the JWT. The Vodafone provided client ID must be used in this claim.

iat

Issued at: UNIX timestamp of when the toke was issued.

jti

JWT ID: unique identifier for the token.

exp

Expiration time: the exact moment the JWT becomes invalid

Example JWT Payload

Example sandbox:

{ "sub": "operatortoken:sandbox_operator_token", "aud": "https://api-sandbox.vf-dmp.engineering.vodafone.com/oauth2/v2/token/{plmnid}", "scope": "dpv:FraudPreventionAndDetection number-verification:device-phone-number:read", "iss": "JFmbgGigniCOKRPblruIvlMvIrBWSBj2807w20T62ROS5nmy", "iat": 1504804131, "exp": 1504807731, "jti": "2a1991e4-eb3a-4eb5-9913-52e97b0c8907" }

Example production:

{ "sub": "operatortoken:prod_operator_token", "aud": "https://api.vf-dmp.engineering.vodafone.com/oauth2/v2/token/{plmnid}", "scope": "dpv:FraudPreventionAndDetection number-verification:device-phone-number:read number-verification:verify", "iss": "JFmbgGigniCOKRPblruIvlMvIrBWSBj2807w20T62ROS5nmy", "iat": 1504804131, "exp": 1504807731, "jti": "2a1991e4-eb3a-4eb5-9913-52e97b0c8907" }

Create JSON Request

Place the encoded and signed JWT into the assertion field within the JSON request body defined in the Number Verification 2.0 API specification.

Example JSON request body:

{ "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "assertion": "eyJraWQiOiJkbXAtcHJvZC1raWQi..." }

Channel Partner Integration

Integration Architecture Overview

diagram showing how the integration works

Key Integration Points

  1. Aggregator/CP ↔ Google: Certificate registration (one-time setup, see below).

  2. Aggregator Backend ↔ Mobile App: DCQL generation & SD-JWT decryption.

  3. Aggregator Backend ↔ Vodafone: OAuth token exchange & phone number verify service API.

Partner Onboarding: Prerequisites & Setup

For access to the service, please contact the Vodafone Network API sales team to complete commercial onboarding.

To be technically onboarded to the service with Vodafone, you are required to provide the following information:

  • The IP address range for allowlisting. This is required as part of our secure connectivity.

  • Provide Vodafone with the required data for Google Registration (see certificate generation steps below):

    • SAN (Subject Alternative Name). This is a value that defines your Android app domain used in the TLS handshake with Google's infrastructure. For example: aggregator.yourcompany.com.

    • Encryption Fingerprint. This is used to encrypt the request and response from the developers application using your service. For example: c8df5c3ac7d8f1abefa8d47ae2133eb7fe3c84b71e22709b3726549000e98c9b.

  • Share JWKS public endpoint. Typicaly https://{your-domain}/.well-known/jwks.json. Used to verify the token's cryptographic signature.

Certificate Generation

Supported Algorithms

ES256 (recommended)

  • Elliptic Curve P-256 (prime256v1) with SHA-256.

  • Smaller keys (256-bit), faster signing.

  • Standard algorithm in Android Credential Manager documentation.

RS256 (alternative)

  • RSA 3072-bit with SHA-256.

  • Larger keys, broader theoretical compatibility.

  • Not documented in Android examples (verify carrier support before use).

Certificate Chain Structure

Root CA Certificate ├─ Common Name: aggregator.yourcompany.com ├─ Key Usage: keyCertSign, cRLSign ├─ Basic Constraints: CA:TRUE ├─ Validity: 10 years └─ Purpose: Signs leaf certificates

Manual Generation

Step 1: Generate Root CA

# Generate EC P-256 private key openssl ecparam -name prime256v1 -genkey -noout -out ca.key

Step 2: Extract Fingerprint (Provide SAN, fingerprint to Vodafone for Google/CP registration)

# Example output: # SAN: aggregator.yourcompany.com # FINGERPRINT:c8df5c3ac7d8f1abefa8d47ae2133eb7fe3c84b71e22709b3726549000e98c9b

Step 3: Generate Leaf Certificate

# Generate leaf private key openssl ecparam -name prime256v1 -genkey -noout -out leaf.key

Step 4: Validate Certificate Chain

# Verify root is self-signed && leaf is signed by root openssl verify -CAfile ca.cert ca.cert openssl verify -CAfile ca.cert leaf.cert

Security Best Practices

  • Private Key Storage: Store ca.key and leaf.key in Hardware Security Module (HSM) or AWS KMS/Azure Key Vault.

  • Never Commit: add *.key to .gitignore.

  • Access Control: Restrict key access to signing service only,

  • Rotation: Rotate leaf certificates annually, root CA every 5-10 years.

  • Backup: Maintain encrypted backups of root CA key in separate secure location.

Phone Number Verify: Integration Flow

Digital Credential Query Language (DCQL) Request Generation

What is DCQL?

The DCQL is a JSON-encoded query language defined as part of OpenID4VP and used to express which credentials a verifier would like to request from a holder.

This request originates from the app backend and the aggregator is responsible for returning the DCQL credential object within the OpenID4VP wrapper, which has a structure similar to the following:

{ "requests": [ { "protocol": "openid4vp-v1-unsigned", "data": { "response_type": "vp_token", "response_mode": "dc_api", "nonce": "<UNIQUE_UUID>", "dcql_query": { "credentials": [ { "id": "<YOUR_AGGREGATOR_NAME_AS_PER_YOUR_CERT_GENERATION>", "format": "dc-authorization+sd-jwt", "claims": [], "meta": { "vct_values": [ "number-verification/device-phone-number/ts43" ], "credential_authorization_jwt": "<SIGNED_CREDENTIAL_AUTH_JWT>" } } ] } } } ] }

Credential Authorisation JWT Structure - Deep Dive

The aggregator backend must generate an ephemeral Elliptic Curve (EC) key pair for response encryption, then build a signed KWT using the Leaf Certificate.

Header:

{ "typ": "oauth-authz-req+jwt", "alg": "ES256", // might be RS256 for you! "x5c": [ "<base64_leaf_cert>", "<base64_root_cert>" ] }

Payload:

{ "iss": "<YOUR_AGGREGATOR_NAME_AS_PER_YOUR_CERT_GENERATION>", "nonce": "<UNIQUE_UUID>", "state": "optional-state-value", "encrypted_response_enc_values_supported": ["A128GCM"], "jwks": { "keys": [{ "kty": "EC", "crv": "P-256", "x": "<ephemeral_public_key_x>", "y": "<ephemeral_public_key_y>", "alg": "ECDH-ES", "use": "enc", "kid": "1" }] }, "consent_data": "<base64_encoded_consent_json>" // see consent data config. below. }

Reference Implementation

Kotlin

fun createCredentialAuthorizationJWT(nonce: String, jwks: Map<String, Any>, leafKey: ECPrivateKey, leafCert: String, rootCert: String): String { val header = JWSHeader.Builder(JWSAlgorithm.ES256) .type(JOSEObjectType("oauth-authz-req+jwt")) .x509CertChain(listOf(Base64.from(leafCert), Base64.from(rootCert))) .build() val claims = JWTClaimsSet.Builder() .issuer(YOUR_AGGREGATOR_NAME_AS_PER_YOUR_CERT_GENERATION) .claim("nonce", nonce) .claim("jwks", jwks) .claim("consent_data", createConsentData()) .build() val signedJWT = SignedJWT(header, claims) signedJWT.sign(ECDSASigner(leafKey)) return signedJWT.serialize() }

Python

def create_credential_authorization_jwt(nonce, jwks, leaf_private_key, leaf_cert, root_cert): """Generates the signed authorization structure entirely on local infrastructure.""" consent_data_b64 = create_consent_data() payload = { "iss":YOUR_AGGREGATOR_NAME_AS_PER_YOUR_CERT_GENERATION, "nonce": nonce, "state": "optional-state-value", "encrypted_response_enc_values_supported": ["A128GCM"], "jwks": jwks, "consent_data": consent_data_b64, } headers = { "typ": "oauth-authz-req+jwt", "alg": "ES256", "x5c": [leaf_cert, root_cert], } return jwt.encode(payload, leaf_private_key, algorithm="ES256", headers=headers)

Consent Data Configuration

Aggregators must dynamically inject user-facing legal declaration text into the JWT payload. The mobile OS utilises this to compile the authorisation interface (device consent pop-up via the credential manager interaction).

Construct a JSON object containing the required policy strings, then base64 encode the entire object and embed it with the jwt payload. Example:

{ "consent_text": "Allow your carrier to share your phone number with the app. The app may use your phone number to manage your account and provide services subject to their ", "policy_link": "https://policies.google.com/terms?hl=en-US", "policy_text": "privacy policy." }

Invoke Android Credential Manager

On receipt of the DCQL Request, the app initiates the credential manager (CM) interaction.

  1. User receives the consent screen pop-up to agree and continue.

  2. On acceptance, the CM completes the SIM based silent-authentication with the carrier network via TS43.

Note: the SD-JWT (Selective response received from the CM can only be decrypted by the aggregator.

Token Exchange: SD-JWT Decryption Pattern

The app receives an encrypted SD-JWT in the format: issuer_jwt~key_binding_jwt.

Response:

{ "protocol": "openid4vp-v1-unsigned", "data": { "vp_token": { "YOUR_AGGREGATOR_NAME_AS_PER_YOUR_CERT_GENERATION": [ "eyJhbGciOiJFUzI1NiIsInR5cCI....signature" ] } } }

Decrypted payload:

{ "claims": { "android_carrier_hint": 28, "app_info": "5f2391... ...402|com.google.android.gms", "carrier_hint": "23415", // <--| extract these for carrier integration "country_code": "gb", "subscription_hint": 3, "vct": "number-verification/device-phone-number/ts43" }, "decrypted_payload": { "temp_token": "4RcVH1ZoysULGM3BxTRyQzqzhetf5ALaUcu+Nf6WyvA=" }, "issuer": "com.google.android.gms", "nonce_verified": "e3f7c4b2-a19c-4319-a9eb-6a7f6ad737ba", "success": true, "temp_token": "4RcVH1ZoysULGM3BxTRyQzqzhetf5ALaUcu+Nf6WyvA=" // <--| extract these for carrier integration }

Suggested Decryption Steps:

  1. Parse & Split: Separate de SD-JWT string by the ~ delimiter into the issuer_jwt and key_binding_jwt components.

  2. Verify Issuer Signature: Extract the X.509 certificate from the issuer_jwt header to verify its signature using com.nimbusds.jose.crypto.ECDSAVerifier, confirming the credential originated from a trusted source (Android Telephony)

  3. Verify Key Binding Signature: Parse the confirmation claim (cnf.jwk) from the verified issuer payload and use this device public key to verify the key_binding_jwt signature, protecting against token injection or swapping.

  4. Validate Nonce: Assert that the nonce claim inside the verified key-binding payload matches the unque session tracking value from the original DCQL request to prevent replay attacks.

  5. Decrypt JWE Payload: Load the cahced session ephemeral private key to decrypt the encrypted_credential block inside the key-binding payload via com.nimbusds.jose.crypto.ECDHDecrypter.

  6. Extract Target Tokens: Parse the decrypted cleartext payload to extract the short-lived temp_token and the carrier_hint needed to downstream token exchange routing.

The temp_token is a short-lived credential (5 minutes expiry), one time use required for Vodafone OAuth exchange.

Reference Implementation

Kotlin

fun decryptAndVerify(sdJwtString: String, expectedNonce: String, ephemeralPrivateKey: ECPrivateKey): Map<String, String> { val parts = sdJwtString.split("~") val issuerJwtStr = parts[0] val keyBindingJwtStr = parts.last().ifEmpty { parts[parts.size - 2] }

Python

def decrypt_and_verify_sd_jwt(sd_jwt_string, expected_nonce, ephemeral_private_key): parts = sd_jwt_string.split("~") issuer_jwt, key_binding_jwt = parts[0], parts[-1] or parts[-2] unverified_header = jwt.get_unverified_header(issuer_jwt) issuer_x5c = f"-----BEGIN CERTIFICATE-----\n{unverified_header['x5c'][0]}\n-----END CERTIFICATE-----" issuer_pub_key = jwk.JWK.from_pem(issuer_x5c.encode()).export_to_pem() issuer_payload = jwt.decode(issuer_jwt, issuer_pub_key, algorithms=["ES256"])

Vodafone Service API Integration

Host: https://api.vf-dmp.engineering.vodafone.com

  • Vodafone OAuth: POST /token/{plmn_id} └─ Body: JWT assertion (signed RS256) └─ Response: access_token

  • Vodafone Resource-1: GET /number-verification/v2/device-phone-number └─ Header: Bearer access_token └─ Response: devicePhoneNumber (string) ~ pattern: ^+[1-9][0-9]{4,14}$

  • Vodafone Resource-2: POST /number-verification/v2/verify └─ Header: Bearer access_token, Body: phoneNumber └─ Response: devicePhoneNumberVerified (boolean)

What is PLMN_ID?

PLMN ID (Public Land Mobile Network) identifies the mobile network operator.

Format: MCCMNC (5-6 digits)

  • MCC: Mobile Country Code (3 digits)

  • MNC: Mobile Network Code (2-3 digits)

Where to Extract it?
From SD-JWT (Recommended)

See Token Exchange: SD-JWT Decryption Pattern for a more detailed view.

# After splitting SD-JWT: issuer~keybinding kb_payload = jwt.decode(key_binding_jwt, options={"verify_signature": False}) plmn_id = kb_payload["carrier_hint"] # "23415"

From Android TelephonyManager (Fallback)

val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManagerval plmn_id = telephonyManager.simOperator // Returns "23415"

Where to Use It

REQUIRED: OAuth Token Endpoint

# PLMN_ID is a mandatory, and MUST be in URL pathoauth_url = f"https://api.vf-dmp.engineering.vodafone.com/oauth2/v2/token/{plmn_id}"

Example URLs:

  • Vodafone UK: .../oauth2/v2/token/23415

  • Vodafone DE: .../oauth2/v2/token/26202

OAuth Token Exchange
JWT Assertion Structure

Header:

{ "alg": "RS256", "kid": "AGGREGATOR-KEY-ID" //Key ID as per your JWKS endpoint (https://{your-domain}/.well-known/jwks.json). }

Payload:

{ "iss": "YOUR_VODAFONE_CLIENT_ID", "sub": "operatortoken:{extracted_temp_token_from_sd_jwt}", "aud": "https://api.vf-dmp.engineering.vodafone.com/oauth2/v2/token/{plmn_id}", "exp": 1736000000, "iat": 1735999700, "jti": "550e8400-e29b-41d4-a716-446655440000", "scope": "dpv:FraudPreventionAndDetection number-verification:device-phone-number:read" }

Sample cURL Command

curl -X POST 'https://api.vf-dmp.engineering.vodafone.com/oauth2/v2/token/23415' \ -H 'Content-Type: application/x-www-form-urlencoded' \ -H 'x-correlator: ce2adc2b-781a-4396-8468-be40a8ccd9be' \ --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \ --data-urlencode 'assertion=eyJraWQiOiJBR0dSRUdBVE9SLUtFWS1JRCIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJZT...{signature}' \ --data-urlencode 'resource=https://api.vf-dmp.engineering.vodafone.com/number-verification/v2/device-phone-number'

Additional Resources

Questions or Issues?

Submit technical support tickets or contact your Vodafone integration team.