Number Verification V1
Number verification confirms that the device uses the phone number provided by the user. When a user enters a phone number on a web page or a device-based app, the backend client makes a request to NaC, which turns to the operator to confirm. Number Verification V1 uses network based authentication.
Number Verification V1 is served at the /number-verification/v0/verify endpoint.
Number Verification V2 is served at the /number-verification/v2/verify endpoint;
see Number Verification V2 — operator token flow.
The number verification API uses a 3-legged OAuth 2.0 flow, where a network subscriber (a device end-user) gives consent through a Network Provider interface. In this process, a mobile app initiates a phone number verification by requesting and receiving an authorization URL. This URL must be accessed via a mobile connection and involves several redirects before a successful response is passed from the Network Provider and Network as Code. This page describes the steps required to gain authorization and successfully execute the consent flow.
Using the Number Verification API requires explicit end‑user consent. Consent may be captured in one of the following ways:
- Directly by the Telecom Operator, or
- By the Application Service Provider (Enterprise), when the Telecom Operator delegates this responsibility.
The applicable approach depends on country‑specific regulations and the consent model adopted by the Telecom Operator. When end‑user consent collection is delegated to the Application Service Provider, your application must implement the following:
Consent collection UI
Your application must present a consent screen that complies with the consent collection requirements published by Network as Code:
This document provides reference UI examples and specifies the exact consent language required for each country.
Consent evidence submission
You must capture screenshots of the consent collection UI implemented in your application and upload them as a single PDF during the application registration process.
These screenshots will be reviewed and approved by the relevant Telecom Operators as part of the Application registration process.
When an enterprise is onboarded to Network as Code, it is provided with client credentials. Developers can obtain their organization credentials via the Dashboard.
NOTE: Remember to subscribe to the Number Verification API first, after which you will be able to click
Test Endpointand use it. Here's a step-by-step on how to subscribe to APIs.
Choose an authorization flow
Both flows use the same OAuth consent steps on the end-user device. They differ in how your backend passes authorization to the verify API.
| Fast authorization flow | Standard authorization flow | |
|---|---|---|
| Best for | Simpler integration; fewer server-side steps | Full OAuth2 control on your backend |
| Authorization URL | fast_flow_csp_auth_endpoint from well-known metadata | authorization_endpoint from well-known metadata |
| Token exchange | Skipped — NaC exchanges the authorization code for you | You call the token_endpoint to obtain an access token |
| Verify API auth | code and state query parameters | Authorization: Bearer access token |
The sections below walk through each flow. Both flows start with the same client credentials step.
Getting client credentials
Getting your client credentials is the first step for either flow:
from network_as_code import NetworkAsCodeApi
client = NetworkAsCodeApi(
rapidapi_host="network-as-code.nokia.rapidapi.com",
api_key="YOUR_API_KEY",
)
response = client.oauth.get_client_credentials()
print(response)The output should be your client ID and secret:
{
"client_id": "your-client-id",
"client_secret": "your-client-secret"
}
Redirect callback handler
After the end user completes consent on their device, the operator redirects to your
redirect_uri with an authorization code and the state you supplied. Your backend
must handle that callback on the end-user device path (the redirect hits your server, but
the user must have opened the authorization URL on the mobile device).
A successful callback looks like:
GET https://example.com/redirect?state=foobar&code=1234
Parse code and state from the query string. Validate that state matches the value
you stored when building the authorization link (see State and nonce).
The redirect does not return nonce.
A minimal handler:
from fastapi import FastAPI
app = FastAPI()
@app.get("/redirect")
async def get_authorization_code(code: str, state: str):
# Validate state against the value bound to this user session before continuing.
print(f"This is your state variable: {state}.")
return f"This is your authorization code: {code}."State and nonce
state and nonce are separate OpenID Connect parameters. Do not validate them at the
same step.
state (redirect callback)
Generate a cryptographically random state (for example UUIDv4) when you build the
authorization URL, store it server-side bound to the user session, and include it in the
link. When the user returns to your redirect_uri, compare the state query parameter to
the stored value. Reject the request if they differ. That mitigates cross-site request
forgery (CSRF): an attacker who tricks the browser into completing authorization should not
be able to pair the resulting code with your session.
nonce (authorization request and ID token)
Generate a separate random nonce, store it server-side with the same session, and send
it on the authorization request. It is not returned on the redirect; the browser
only receives code and state.
Per OpenID Connect, the nonce you sent must appear as a claim in the id_token
returned from the token endpoint. That lets your backend confirm the ID token belongs to
the authorization request you started, which mitigates mix-up of authorization codes or ID
token replay. Network as Code also checks the operator ID token nonce when exchanging
codes on your behalf.
| Flow | What your backend should do with nonce |
|---|---|
| Standard | After POST to the token endpoint, decode id_token (JWT) and verify its nonce claim equals the value you stored. Then use access_token for verify. |
| Fast | Send nonce on the authorization URL. You do not receive an id_token on your backend; NaC performs the token exchange when you call verify with code and state. |
Note:
nonceis mandatory in the authorization request. Number verification authorization requests should also setprompt=none, because the mobile network connection authenticates the subscription.
Fast authorization flow
The fast flow uses an abridged OAuth2 path: you skip the single-use access token exchange
and pass the authorization code and state directly to the verify API. Network as Code
exchanges the code for an access token behind the scenes.
Getting the fast authorization endpoint
The well-known metadata endpoint returns the fast authorization URL together with the standard OAuth endpoints:
from network_as_code import NetworkAsCodeApi
client = NetworkAsCodeApi(
rapidapi_host="network-as-code.nokia.rapidapi.com",
api_key="YOUR_API_KEY"
)
response = client.well_known_metadata.get_oauth_authorization_server()
print(response)Example response:
{
"authorization_endpoint": "https://some-auth-server.net/oauth2/v1/authorize",
"token_endpoint": "https://some-auth-server.net/oauth2/v1/token",
"fast_flow_csp_auth_endpoint": "https://some-auth-server.net/oauth2/v1/retrieve_csp_auth_url"
}
Use fast_flow_csp_auth_endpoint as the base URL when building the authorization link in the next step.
Getting the authorization code
Construct an authorization URL from fast_flow_csp_auth_endpoint with the parameters
below. The end user's device must open this URL (not your backend) so the operator can
bind consent to that device.
| Parameters | Type | Description | Mandatory or Optional |
|---|---|---|---|
authorization_endpoint | string | Use fast_flow_csp_auth_endpoint from the previous step, e.g. https://some-auth-server.net/oauth2/v1/retrieve_csp_auth_url. | Mandatory |
scope | string | Consent purpose and scope: dpv:FraudPreventionAndDetection number-verification:verify. | Mandatory |
response_type | string | Must be code. | Mandatory |
client_id | string | Client ID from Getting client credentials. | Mandatory |
redirect_uri | string | Your backend GET callback, e.g. https://example.com/redirect. | Mandatory |
login_hint | string | End-user phone number in E.164 form, e.g. "+99999991000", to route the request to the right network. | Mandatory |
prompt | string | Must be none for number verification (network-based authentication). | Mandatory |
state | string | Random value stored server-side; returned on redirect. Validate at callback. See State and nonce. | Mandatory |
nonce | string | Random value stored server-side; sent on authorize, echoed in id_token (standard flow). See State and nonce. | Mandatory |
URL-encode the query string:
{authorization_endpoint}?scope={scope}&state={state}&response_type=code&prompt=none&client_id={client_id}&redirect_uri={redirect_uri}&login_hint={login_hint}&nonce={nonce}
Present the link to the user (button, redirect, or in-app web view). On success, the
browser calls your redirect callback handler, typically with
both code and state in the query string.
Continue to Verifying a number using the fast-flow examples (pass
code and state to the verify API).
Standard authorization flow
The standard flow follows typical OAuth2: after the redirect callback you exchange the authorization code for a single-use access token, then call verify with that token.
Getting the authorization and token endpoints
from network_as_code import NetworkAsCodeApi
client = NetworkAsCodeApi(
rapidapi_host="network-as-code.nokia.rapidapi.com",
api_key="YOUR_API_KEY"
)
response = client.well_known_metadata.get_oauth_authorization_server()
print(response)For the standard flow you use authorization_endpoint and token_endpoint:
{
"authorization_endpoint": "https://some-auth-server.example.com/oauth2/v1/authorize",
"token_endpoint": "https://some-auth-server.example.com/oauth2/v1/token"
}
Getting the authorization code
Build an authorization link from authorization_endpoint using the same parameter table
as the fast flow, but set authorization_endpoint to the standard authorize URL (not
fast_flow_csp_auth_endpoint).
| Parameters | Type | Description | Mandatory or Optional |
|---|---|---|---|
authorization_endpoint | string | authorization_endpoint from well-known metadata, e.g. https://some-auth-server.example.com/oauth2/v1/authorize. | Mandatory |
scope | string | dpv:FraudPreventionAndDetection number-verification:verify. | Mandatory |
response_type | string | Must be code. | Mandatory |
client_id | string | Client ID from Getting client credentials. | Mandatory |
redirect_uri | string | Your backend GET callback, e.g. https://example.com/redirect. | Mandatory |
login_hint | string | End-user phone number, e.g. "+99999991000". | Mandatory |
prompt | string | Must be none for number verification. | Mandatory |
state | string | Validated on redirect. See State and nonce. | Mandatory |
nonce | string | Validated via id_token after token exchange (standard flow). See State and nonce. | Mandatory |
The user opens the URL on their device. On success, your redirect callback handler receives the NaC authorization code, for example:
https://example.com/redirect?code=your-code-will-appear-here&state=your-state
Obtaining a single-use access token
After you have the authorization code, exchange it for an access token. Call the token endpoint once per number verification for that end-user device.
curl -X POST https://some-auth-server.example.com/oauth2/v1/token \
-d client_id=previously-obtained-client-id \
-d client_secret=previously-obtained-client-secret \
-d grant_type=authorization_code \
-d code=previously-obtained-nac-authorization-code
| Parameters | Type | Description | Mandatory or Optional |
|---|---|---|---|
client_id | string | From Getting client credentials. | Mandatory |
client_secret | string | From Getting client credentials. | Mandatory |
grant_type | string | Must be authorization_code. | Mandatory |
code | string | Authorization code from the redirect callback. | Mandatory |
Successful response:
{
"access_token": "this-is-your-token",
"token_type": "Bearer",
"expires_in": 1723475356,
"id_token": "eyJ..."
}
Decode id_token and verify that its nonce claim matches the nonce you stored when
building the authorization URL. Only then trust the token pair and continue to
Verifying a number using the standard-flow examples (Bearer access
token).
Verifying a number
Call the verify API with the phone number to check. Use login_hint during authorization
and the same number (or its hash) in the verify request.
Standard flow (access token)
Pass the access token via the Authorization header. With the SDK, set authorization to
the full header value (for example Bearer plus the token from the token endpoint).
from network_as_code import NetworkAsCodeApi
client = NetworkAsCodeApi(
rapidapi_host="network-as-code.nokia.rapidapi.com",
api_key="YOUR_API_KEY"
)
token = "Bearer YOUR_ACCESS_TOKEN"
response = client.number_verification.verify(
authorization=token,
phone_number="+99999991000",
)
print(response.device_phone_number_verified)Fast flow (authorization code and state)
Pass the code and state from your redirect callback as query parameters. They must
match the values used when building the authorization link.
from network_as_code import NetworkAsCodeApi
client = NetworkAsCodeApi(
rapidapi_host="network-as-code.nokia.rapidapi.com",
api_key="YOUR_API_KEY"
)
state = "YOUR_STATE"
code = "YOUR_AUTHORIZATION_CODE"
response = client.number_verification.verify(
state=state,
code=code,
phone_number="+99999991000",
)
print(response.device_phone_number_verified)Verify response
A successful verify returns a JSON object with devicePhoneNumberVerified (not a bare
boolean). See Number Verification API HTTP responses
for full response and error shapes.
| Field | Type | Description |
|---|---|---|
devicePhoneNumberVerified | boolean | true if the supplied phone number matches the SIM on the authenticated device; false otherwise. |
In the SDK, read device_phone_number_verified (Python) or devicePhoneNumberVerified (TypeScript).
Return the device phone number
To retrieve the authenticated device's MSISDN instead of comparing a number, use the phone
number share operation after the same OAuth flows. The response field is
devicePhoneNumber; see the Number share tabs on the HTTP responses
page.
response = client.number_verification_v100.phone_number_share(
authorization="Bearer YOUR_ACCESS_TOKEN",
)
print(response.device_phone_number)For the fast flow, pass code and state instead of authorization, in the same way as verify.
Simulated Number Verification scenarios
The Network as Code simulators have been configured to provide specific number verification results for specific simulated devices based on their device identifier. This will be helpful in testing your code against the different responses, including possible errors, by simply substituting the device identifier in your code.
The device identifiers and their responses can be found in the following table:
| Device identifier type | device identifier | HTTP status code | HTTP status code description | Response description |
|---|---|---|---|---|
| Phone Number | +99999991000 | 200 | Success | Number verifies correctly |
| Phone Number | +99999991001 | 200 | Success | Number is not verified |
| Phone Number | +99999990400 | 400 | Bad Request | |
| Phone Number | +99999990404 | 404 | Not found | |
| Phone Number | +99999990422 | 422 | Unprocessable Content | |
| Phone Number | +99999990500 | 500 | Internal Server Error | |
| Phone Number | +99999990502 | 502 | Bad Gateway | |
| Phone Number | +99999990503 | 503 | Service Unavailable | |
| Phone Number | +99999990504 | 504 | Gateway Timeout |
Last updated September 18, 2026