> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Phone Verification Report

> Parse Phone Verification responses: carrier data, line type, OTP lifecycle, cross-session matches, and risk warnings. Pay-per-OTP from $0.04 + carrier fees.

The Phone Verification report captures the full outcome of a phone OTP challenge: who we sent the code to, which carrier and channel handled it, how many attempts it took, whether the number is disposable or VoIP, and any cross-session matches against your blocklist or other users' sessions.

This page documents the JSON shape returned by the decision endpoint so you can parse OTP outcomes, carrier metadata, and risk flags for each verified number.

<Frame>
  <img src="https://mintcdn.com/didit-0f962782/z6T2GHM4Zh-iSj-K/images/phone-verification-report.png?fit=max&auto=format&n=z6T2GHM4Zh-iSj-K&q=85&s=9b5c6c6112303ebc60c0f287aafdb996" alt="Didit phone verification report showing OTP outcome, carrier metadata and risk flags" width="2929" height="2499" data-path="images/phone-verification-report.png" />
</Frame>

## Overview

A phone report is produced every time a workflow node runs the Phone Verification feature. Each report represents one OTP challenge against one phone number and contains:

* The phone number broken into prefix, national number and full E.164 form, plus ISO country code and country name.
* Carrier metadata (`name`, `type`) resolved from the destination network.
* Boolean risk flags (`is_disposable`, `is_virtual`) derived from line-type analysis.
* The actual delivery channel used (`verification_method`) and the count of OTP send attempts.
* A chronological `lifecycle[]` log of every send, retry, delivery event, and code-check attempt.
* A `matches[]` array surfacing the same number on other sessions — any status, across KYC, KYB and standalone API verifications — or your blocklist.
* A `warnings[]` array — risk events emitted during the verification (see [Phone Verification warnings](/core-technology/phone-verification/warnings-phone-verification)).
* A `node_id` that identifies which workflow graph node produced the report (V3 sessions only).

In hosted verification flows Didit runs the OTP exchange for you. For direct API integrations the same flow is two calls — [`POST /v3/phone/send/`](/standalone-apis/phone-send) to deliver the code and [`POST /v3/phone/check/`](/standalone-apis/phone-check) to validate the user's entry. A pending OTP is valid for **5 minutes from the first send** (retries do not extend the window). In workflow sessions users get 2 OTP send attempts (`phone_max_retries`) and 2 code-entry attempts (`phone_max_check_attempts`) by default — both tunable per workflow node; exhausting either cap finalizes the step as `Declined` with `VERIFICATION_CODE_ATTEMPTS_EXCEEDED`. The standalone phone API allows 3 code-entry attempts per verification.

## Where it appears in API responses

The decision endpoint (`GET /v3/session/{sessionId}/decision/`) returns phone reports under the plural array key **`phone_verifications`**. The array contains one entry per Phone Verification node in the workflow graph — typically one, but step-up flows may produce several.

```json theme={null}
{
  "session_id": "11111111-1111-1111-1111-111111111111",
  "status": "Approved",
  "phone_verifications": [
    { "status": "Approved", "node_id": "feature_phone_1", "...": "..." }
  ]
}
```

A `null` value means no Phone Verification step has run yet. Iterate the array (rather than reading `phone_verifications[0]`) when your workflow can collect more than one phone number.

## Schema

The canonical field-by-field schema lives on the [Data models](/reference/data-models#phone-verification) page.

```typescript theme={null}
interface PhoneVerification {
  status: "Not Finished" | "Approved" | "Declined" | "In Review" | "Expired";
  phone_number_prefix: string;       // E.g. "+34"
  phone_number: string;              // National number, no prefix
  full_number: string;               // Full E.164, e.g. "+34600600600"
  country_code: string;              // ISO 3166-1 alpha-2, e.g. "ES"
  country_name: string;
  carrier: {
    name: string;
    type:                              // From LineTypeChoices
      | "mobile" | "fixed_line" | "voip" | "isp" | "vpn"
      | "toll_free" | "premium_rate" | "shared_cost" | "local_rate"
      | "satellite" | "pager" | "payphone" | "voice_mail"
      | "calling_cards" | "service" | "short_codes_commercial"
      | "universal_access" | "other" | "unknown";
  };
  is_disposable: boolean;            // Temporary / burner number
  is_virtual: boolean;               // true when carrier.type is voip, isp or vpn
  verification_method:               // Actual channel that delivered the OTP
    | "sms" | "whatsapp" | "telegram" | "voice" | "rcs" | "viber" | "zalo";
  verification_attempts: number;     // OTP send attempts (initial send + resends)
  verified_at: string | null;        // ISO 8601, set when a valid OTP was entered
  warnings: Warning[];               // Risk events emitted during verification
  lifecycle: PhoneLifecycleEvent[];  // OTP send/retry/delivery/check timeline
  matches: PhoneMatch[];             // Cross-session / blocklist matches (max 5)
  node_id: string;                   // Workflow graph node that produced this report
}
```

### Status values

| Status         | Meaning                                                                                                                   |
| -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `Not Finished` | The OTP send has been initiated but the verification has not been finalized yet.                                          |
| `Approved`     | The user entered a valid OTP and no declining risk matched.                                                               |
| `Declined`     | An auto-decline warning fired (blocklist, high-risk, attempts exceeded) or a risk action configured to `DECLINE` matched. |
| `In Review`    | A risk action configured to `REVIEW` routed the step to manual review.                                                    |
| `Expired`      | The 5-minute OTP window elapsed with no valid code — applies to verifications created via the standalone phone API.       |

### Lifecycle event types

`lifecycle[]` is sorted chronologically. Each event has `type`, `timestamp`, `details`, and a billable `fee` (when applicable):

| Event type                              | Emitted when                                                                 |
| --------------------------------------- | ---------------------------------------------------------------------------- |
| `PHONE_VERIFICATION_MESSAGE_SENT`       | First OTP send attempt over the requested channel.                           |
| `PHONE_VERIFICATION_RETRY_MESSAGE_SENT` | Subsequent OTP send after the user requested a resend.                       |
| `PHONE_VERIFICATION_BLOCKED`            | The send was blocked (spam, suspicious, repeated, invalid number).           |
| `PHONE_DELIVERY_DELIVERED`              | Carrier confirmed delivery on the actual channel used.                       |
| `PHONE_DELIVERY_UNDELIVERABLE`          | Carrier reported the message could not be delivered.                         |
| `VALID_CODE_ENTERED`                    | The user submitted the correct OTP code.                                     |
| `INVALID_CODE_ENTERED`                  | The user submitted an incorrect OTP code (counts toward the code-entry cap). |
| `PHONE_VERIFICATION_APPROVED`           | Feature-level status was set to `Approved`.                                  |
| `PHONE_VERIFICATION_DECLINED`           | Feature-level status was set to `Declined`.                                  |
| `PHONE_VERIFICATION_IN_REVIEW`          | Feature-level status was set to `In Review`.                                 |
| `PHONE_VERIFICATION_EXPIRED`            | The OTP window elapsed without a valid code.                                 |

The `details` payload depends on the event family:

* **Send events** (`PHONE_VERIFICATION_MESSAGE_SENT`, `PHONE_VERIFICATION_RETRY_MESSAGE_SENT`, `PHONE_VERIFICATION_BLOCKED`) — `{ status, reason, channel, actual_channel }`. `status` is `Success`, `Retry` or `Blocked`; `reason` is `null` unless the send was blocked (`invalid_phone_number`, `repeated_attempts`, `suspicious`, `spam`, `unknown`); `channel` is the requested channel and `actual_channel` the channel that actually delivered (e.g. a WhatsApp send that fell back to SMS).
* **Delivery events** (`PHONE_DELIVERY_DELIVERED`, `PHONE_DELIVERY_UNDELIVERABLE`) — `{ channel, status }` with `status` `delivered` or `undeliverable`.
* **Check events** (`VALID_CODE_ENTERED`, `INVALID_CODE_ENTERED`) — `{ code_tried, status }` with `status` `Approved`, `Declined`, `Failed` or `Expired or Not Found`.
* **Final status events** — `null`, except `PHONE_VERIFICATION_DECLINED` / `PHONE_VERIFICATION_IN_REVIEW`, which carry `{ "reason": "<risk code>" }` (e.g. `PHONE_NUMBER_IN_BLOCKLIST`).

`fee` on a send event is the estimated price until delivery is confirmed, then the actual billed amount — delivery is billed per delivered message when the verification finalizes, and only `Blocked` sends are guaranteed free. Delivery, check and status events always carry `fee: 0`.

### Cross-session matches

`matches[]` records other sessions in the same application where the same number was seen — across KYC, KYB and standalone API verifications, regardless of their status — plus blocklist hits configured in the [management-api lists](/management-api/lists/overview). Sessions sharing the current session's `vendor_data` are excluded (the same end-user does not match themselves), and the array is capped at **5** entries. When the number is on your blocklist but none of the matched sessions is blocklisted, a synthetic entry with `source: "list_entry"` (all session fields `null`) is prepended to the array.

```typescript theme={null}
interface PhoneMatch {
  session_id: string | null;         // null when source = "list_entry"
  session_number: number | null;
  vendor_data: string | null;
  verification_date: string | null;  // ISO 8601, creation date of the matched session
  phone_number: string;
  status: string | null;             // Status of the matched session
  is_blocklisted: boolean;
  api_service: string | null;        // Set when the matched session came from a standalone API
  source: "session" | "list_entry";  // "list_entry" = manual blocklist hit
}
```

## Examples

### Approved — WhatsApp OTP, mobile carrier

```json theme={null}
{
  "status": "Approved",
  "phone_number_prefix": "+34",
  "phone_number": "600600600",
  "full_number": "+34600600600",
  "country_code": "ES",
  "country_name": "Spain",
  "carrier": { "name": "Orange", "type": "mobile" },
  "is_disposable": false,
  "is_virtual": false,
  "verification_method": "whatsapp",
  "verification_attempts": 1,
  "verified_at": "2025-08-24T09:12:39.684292Z",
  "warnings": [],
  "lifecycle": [
    {
      "type": "PHONE_VERIFICATION_MESSAGE_SENT",
      "timestamp": "2025-08-24T09:12:30.580554Z",
      "details": { "status": "Success", "reason": null, "channel": "whatsapp", "actual_channel": "whatsapp" },
      "fee": 0.04
    },
    {
      "type": "PHONE_DELIVERY_DELIVERED",
      "timestamp": "2025-08-24T09:12:31.000000Z",
      "details": { "channel": "whatsapp", "status": "delivered" },
      "fee": 0
    },
    {
      "type": "VALID_CODE_ENTERED",
      "timestamp": "2025-08-24T09:12:39.662157Z",
      "details": { "code_tried": "123456", "status": "Approved" },
      "fee": 0
    },
    {
      "type": "PHONE_VERIFICATION_APPROVED",
      "timestamp": "2025-08-24T09:12:39.684292Z",
      "details": null,
      "fee": 0
    }
  ],
  "matches": [],
  "node_id": "feature_phone_1"
}
```

### Declined — blocklisted VoIP number

The user entered a valid OTP, but finalization found the number on the application's blocklist (auto-decline) and flagged its VoIP line type (`voip_number_action` left at `NO_ACTION`, so it logs as `information`). Because no matched session is blocklisted, the blocklist hit appears as a synthetic `list_entry` match.

```json theme={null}
{
  "status": "Declined",
  "phone_number_prefix": "+1",
  "phone_number": "5551234567",
  "full_number": "+15551234567",
  "country_code": "US",
  "country_name": "United States",
  "carrier": { "name": "VoIP Carrier", "type": "voip" },
  "is_disposable": false,
  "is_virtual": true,
  "verification_method": "sms",
  "verification_attempts": 1,
  "verified_at": "2025-08-24T09:11:10.221340Z",
  "warnings": [
    {
      "feature": "PHONE",
      "risk": "PHONE_NUMBER_IN_BLOCKLIST",
      "additional_data": { "blocklisted_session_id": null, "blocklisted_session_number": null, "api_service": null },
      "log_type": "error",
      "short_description": "Phone number in blocklist",
      "long_description": "The system detected that the phone number is in the blocklist, which is not allowed.",
      "node_id": "feature_phone_1"
    },
    {
      "feature": "PHONE",
      "risk": "VOIP_NUMBER_DETECTED",
      "additional_data": null,
      "log_type": "information",
      "short_description": "VoIP number detected",
      "long_description": "The system detected that the phone number is a VoIP number, which is not allowed.",
      "node_id": "feature_phone_1"
    }
  ],
  "lifecycle": [
    {
      "type": "PHONE_VERIFICATION_MESSAGE_SENT",
      "timestamp": "2025-08-24T09:11:00.000000Z",
      "details": { "status": "Success", "reason": null, "channel": "sms", "actual_channel": "sms" },
      "fee": 0.04
    },
    {
      "type": "VALID_CODE_ENTERED",
      "timestamp": "2025-08-24T09:11:10.221340Z",
      "details": { "code_tried": "482917", "status": "Approved" },
      "fee": 0
    },
    {
      "type": "PHONE_VERIFICATION_DECLINED",
      "timestamp": "2025-08-24T09:11:10.350000Z",
      "details": { "reason": "PHONE_NUMBER_IN_BLOCKLIST" },
      "fee": 0
    }
  ],
  "matches": [
    {
      "session_id": null,
      "session_number": null,
      "vendor_data": null,
      "verification_date": null,
      "phone_number": "+15551234567",
      "status": null,
      "is_blocklisted": true,
      "api_service": null,
      "source": "list_entry"
    }
  ],
  "node_id": "feature_phone_1"
}
```

## Related

* [Phone Verification overview](/core-technology/phone-verification/overview) — feature behavior, pricing, supported channels.
* [Phone Verification warnings](/core-technology/phone-verification/warnings-phone-verification) — every risk code, decline triggers and configurable actions.
* [Data models — phone verification](/reference/data-models#phone-verification) — canonical field-by-field schema.
* [Standalone phone API](/standalone-apis/phone-send) — `POST /v3/phone/send/` and [`POST /v3/phone/check/`](/standalone-apis/phone-check) endpoints.
* [Management API — Lists](/management-api/lists/overview) — manage the phone blocklist that feeds `matches[]` with `source: list_entry`.
