Number Verification V2 — operator token flow
Number Verification V2 adds SIM-based authentication using a GSMA TS.43 operator token. Unlike the network-based flow in Number Verification V1, this path does not require the device to be on a cellular data connection at the time of verification. Wi-Fi and other connections are fine, as long as the device can obtain an operator token from the SIM.
Number Verification V2 is served at the /number-verification/v2/verify endpoint.
Number Verification V1 is served at the /number-verification/v0/verify endpoint.
This page covers the verify number use case: confirm that the phone number the user entered
matches the number on the device. The flow uses OpenID4VP on your backend, the Android Credential
Manager on the device, and the Number Verification V2 verify API with the credential field.
When to use this flow
| Operator token flow (this page, V2) | Network-based flow (V1) | |
|---|---|---|
| Connection | Wi-Fi, cellular, or other | Cellular data on the device (not Wi-Fi or hotspot) |
| User steps | OS consent dialog via Credential Manager | Open authorization URL on the device |
| Platform | Android with up-to-date Google Play Services | Any browser or app that can open the auth URL |
| Best for | Native Android apps where users may be off the home network | Web or simpler integrations on cellular |
If the device cannot produce an operator token (unsupported OS, missing Play Services, user declined consent), fall back to the V1 network-based flow.
Overview
Your backend drives the flow. The mobile app is a pass-through for the Credential Manager step; it never sees decryption keys or the operator token in cleartext.

- The app asks its backend to start verification for a phone number.
- The backend builds an incomplete OpenID4VP request and sends it to Network as Code. NaC signs the request and returns a completed DCQL payload.
- The app passes that payload to the Android Credential Manager. The user consents in an OS dialog.
- The app forwards the encrypted SD-JWT response to its backend.
- The backend calls Number Verification V2
/verifywith the phone number and the SD-JWT in thecredentialfield. Network as Code decrypts the credential, exchanges the operator token with the operator, and returns the result.
Certificate management, aggregator registration, and operator-token exchange happen inside Network as Code. Your integration only touches the steps above.
Prerequisites
- An active Network as Code subscription and API key.
- An Android app targeting devices with current Google Play Services.
- A backend able to make server-to-server HTTPS calls to Network as Code (the OpenID4VP and verify steps are never called directly from the mobile app).
- A SIM from an operator that supports the TS.43 operator token flow in the target market. If the operator does not support it, use Number Verification V1 instead.
Step 1 — Build an incomplete OpenID4VP request
On your backend, construct an OpenID4VP request object that describes the credential you need.
Leave credential_authorization_jwt out of the DCQL credential meta object — Network as Code fills that in.
Generate a fresh, cryptographically random nonce for each verification attempt.
Store the nonce server-side; you will need it to correlate the Credential Manager response later.
Example incomplete request:
{
"response_type": "vp_token",
"response_mode": "dc_api.jwt",
"nonce": "1137c18a-1d52-4952-90b5-81f6a36f63e4",
"state": "your-server-side-session-id",
"dcql_query": {
"credentials": [
{
"id": "aggregator-nac",
"format": "dc-authorization+sd-jwt",
"meta": {
"vct_values": [
"number-verification/verify/ts43"
]
},
"claims": [
{
"path": ["subscription_hint"],
"values": [1]
},
{
"path": ["phone_number_hint"],
"values": ["+358401234567"]
}
]
}
]
}
}
| Field | Description |
|---|---|
nonce | Single-use random value for this verification attempt. |
state | Optional. Echoed back by Network as Code; use it to correlate this verification attempt with a server-side session. |
vct_values | Must be number-verification/verify/ts43. The Network as Code operator token flow currently supports the verify use case only. |
phone_number_hint | Optional. The E.164 number the user entered, to guide SIM selection in multi-SIM setups. |
subscription_hint | Optional. Numeric SIM slot index (for example 1 for the first SIM) in multi-SIM devices. |
Treat the completed DCQL JSON you receive from Network as Code as an opaque blob when handing it to the client. Do not modify it after NaC returns it.
Step 2 — Complete the request with Network as Code
Send the incomplete OpenID4VP object to Network as Code.
NaC adds the signed credential_authorization_jwt (including encryption keys and consent text for the OS dialog)
and returns the completed request.
curl --request POST \
--url 'https://network-as-code.p-eu.rapidapi.com/oauth2/v1/auth/openid4vp' \
--header 'Content-Type: application/json' \
--header 'X-RapidAPI-Host: network-as-code.nokia.rapidapi.com' \
--header 'X-RapidAPI-Key: YOUR-API-KEY' \
--data '{
"response_type": "vp_token",
"response_mode": "dc_api.jwt",
"nonce": "1137c18a-1d52-4952-90b5-81f6a36f63e4",
"state": "your-server-side-session-id",
"dcql_query": {
"credentials": [
{
"id": "aggregator-nac",
"format": "dc-authorization+sd-jwt",
"meta": {
"vct_values": ["number-verification/verify/ts43"]
},
"claims": [
{
"path": ["subscription_hint"],
"values": [1]
},
{
"path": ["phone_number_hint"],
"values": ["+358401234567"]
}
]
}
]
}
}'
This is the same body constructed in Step 1, sent as-is to Network as Code.
A successful response is the same OpenID4VP structure with credential_authorization_jwt populated
inside dcql_query.credentials[].meta. Return this JSON to your mobile app.
Step 3 — Obtain the credential on the device
Pass the completed OpenID4VP JSON from your backend to the Android app. The app invokes the Android Credential Manager to present the OS consent dialog and retrieve an encrypted verifiable presentation (SD-JWT).
The SD-JWT is encrypted end-to-end between the device and Network as Code. Your app must not attempt to decrypt or parse it — forward the response to your backend as-is.
For platform integration details, required dependencies, and request formatting for Credential Manager, see the upstream guide: Android phone number verification with digital credentials.
At a high level, the Credential Manager response has this shape:
{
"protocol": "openid4vp-v1-unsigned",
"data": {
"vp_token": {
"aggregator-nac": ["eyJ...~eyJ..."]
}
}
}
Extract the SD-JWT string from data.vp_token (the key matches the id you used in the DCQL credential).
Send that string to your backend together with the nonce from step 2.
Step 4 — Verify the phone number
Call the Number Verification V2 verify endpoint with:
- The phone number to check, as either
phoneNumberorhashedPhoneNumber. - The SD-JWT from the Credential Manager in the required
credentialfield. - Optionally, an
x-correlatorheader for request tracing.
Authenticate the request with your Network as Code API key.
Number Verification V2 requires a 3-legged authorization with the number-verification:verify
scope. Network as Code performs that authorization on your behalf by exchanging the TS.43
operator token supplied in credential with the operator, so your backend does not need to
run a separate authorization code flow for this path.
| Field | Type | Required | Description |
|---|---|---|---|
phoneNumber | string | one of* | E.164 phone number to verify, prefixed with +. |
hashedPhoneNumber | string | one of* | SHA-256 hash (hex-encoded) of the E.164 phone number, including the + prefix. |
credential | string | Yes | SD-JWT from the Android Credential Manager containing the TS.43 operator token. |
* Exactly one of phoneNumber or hashedPhoneNumber must be present. Sending both, or
neither, returns a 400 INVALID_ARGUMENT error.
curl --request POST \
--url 'https://network-as-code.p-eu.rapidapi.com/passthrough/camara/v1/number-verification/number-verification/v2/verify' \
--header 'Content-Type: application/json' \
--header 'X-RapidAPI-Host: network-as-code.nokia.rapidapi.com' \
--header 'X-RapidAPI-Key: YOUR-API-KEY' \
--header 'x-correlator: b4333c46-49c0-4f62-80d7-f0ef930f1c46' \
--data '{
"phoneNumber": "+358401234567",
"credential": "eyJ...~eyJ..."
}'
Alternatively, verify with a hashed phone number:
curl --request POST \
--url 'https://network-as-code.p-eu.rapidapi.com/passthrough/camara/v1/number-verification/number-verification/v2/verify' \
--header 'Content-Type: application/json' \
--header 'X-RapidAPI-Host: network-as-code.nokia.rapidapi.com' \
--header 'X-RapidAPI-Key: YOUR-API-KEY' \
--data '{
"hashedPhoneNumber": "a32f67ab4e4312618b09cd23ed8ce41b13e095fe52b73b2e8da8ef49830e50db",
"credential": "eyJ...~eyJ..."
}'
Verify response
A successful response:
{
"devicePhoneNumberVerified": true
}
| Field | Type | Description |
|---|---|---|
devicePhoneNumberVerified | boolean | true if the supplied number matches the SIM on the device; false otherwise. |
Error responses
| HTTP status | Code | Meaning |
|---|---|---|
| 400 | INVALID_ARGUMENT | Malformed request, for example both phoneNumber and hashedPhoneNumber supplied, or an invalid E.164 value. |
| 401 | UNAUTHENTICATED | Missing, invalid, or expired credentials. |
| 403 | PERMISSION_DENIED | Insufficient permission or scope for Number Verification. |
| 403 | NUMBER_VERIFICATION.USER_NOT_AUTHENTICATED_BY_MOBILE_NETWORK | The user was not authenticated via the mobile network. |
| 404 | NOT_FOUND | The requested resource does not exist. |
| 422 | UNPROCESSABLE_CONTENT | The request was well-formed but could not be processed, for example an invalid or undecryptable credential. |
| 5xx | INTERNAL, NOT_AVAILABLE, TIMEOUT | Server-side or upstream operator error. Retry with backoff or fall back to Number Verification V1. |
This table lists the most common errors. POST /oauth2/v1/auth/openid4vp (step 2) returns 422
with a detail array of validation errors if the incomplete OpenID4VP request is malformed.
See Number Verification API HTTP responses for full response shapes.
Error handling
| Situation | What to do |
|---|---|
| User declines the OS consent dialog | Treat as verification cancelled. Offer Number Verification V1 if appropriate. |
nonce reused or expired | Start a new flow from step 1 with a fresh nonce and a new OpenID4VP request. |
Invalid or undecryptable credential | Network as Code returns an error. Do not retry with the same SD-JWT; restart from step 1. |
| Device lacks Credential Manager support | Fall back to Number Verification V1. |
Security notes
- Never embed signing keys, decryption keys, or aggregator certificates in the mobile app.
Only your backend talks to
POST /oauth2/v1/auth/openid4vp. - The SD-JWT in
credentialis encrypted for Network as Code. The app is a transport layer only. - Generate a new
nonceand OpenID4VP request for every verification attempt. Do not cache or reuse completed DCQL payloads across sessions. - Bind the verification session on your backend (for example with a server-side session ID) so a captured SD-JWT cannot be replayed against a different user context.
Related documentation
- Number Verification overview
- Number Verification V1 — network-based flow
- Consent and identity management
- HTTP status codes
Last updated September 18, 2026