Skip to main content
The Email Verification report captures the full outcome of an email OTP challenge: who the message was sent to, whether the address is disposable or undeliverable, whether it appears in known data breaches, how many attempts the user took, and any cross-session matches against your blocklist or other approved users. This page documents the JSON shape returned by the decision endpoint so you can parse OTP outcomes, breach exposure, and risk flags for each verified address.
Didit email verification report showing OTP outcome, breach data and risk flags

Overview

An email report is produced every time a workflow node runs the Email Verification feature. Each report represents one OTP challenge against one address and contains:
  • The verified email address.
  • Boolean risk flags (is_breached, is_disposable, is_undeliverable) and the supporting breaches[] array sourced from a breach-intelligence database.
  • The count of OTP send attempts (verification_attempts) and the approval timestamp.
  • A chronological lifecycle[] log of every send, retry and code-check attempt.
  • A matches[] array surfacing the same address on other approved email verifications or your blocklist.
  • A warnings[] array — risk events emitted during the verification (see Email Verification warnings).
  • A node_id that identifies which workflow graph node produced the report (V3 sessions only).
In hosted workflow sessions the OTP challenge runs inside the Didit verification UI — you only read the result from the decision payload. For server-to-server OTP without a hosted flow, use the standalone Email API: POST /v3/email/send/ delivers the code and POST /v3/email/check/ validates the user’s entry; finalized standalone verifications surface through the same report shape. Hosted sessions cap the user at 2 wrong code entries (email_max_check_attempts) and 2 OTP sends — the initial send plus one resend (email_max_retries) by default, tunable per workflow node. Exceeding either cap finalizes the step as Declined with EMAIL_CODE_ATTEMPTS_EXCEEDED.

Where it appears in API responses

The decision endpoint (GET /v3/session/{sessionId}/decision/) returns email reports under the plural array key email_verifications. The array contains one entry per Email Verification node in the workflow graph — typically one, but step-up flows may produce several.
A null value means no Email Verification step has run yet. Iterate the array (rather than reading email_verifications[0]) when your workflow can collect more than one email address.

Schema

The canonical field-by-field schema lives on the Data models page.
An undeliverable address — invalid syntax, a non-existent domain, missing MX records, or a failed OTP delivery — finalizes the step as Declined with the auto-decline warning UNDELIVERABLE_EMAIL_DETECTED. The only exception: when the user typed the address themselves (it was not pre-filled at session creation), an undeliverable send returns an inline error so they can retry with a different address instead of being declined immediately.

Status values

Lifecycle event types

lifecycle[] is sorted chronologically. Each event has type, timestamp, details and a billable fee (when applicable): The details payload depends on the event family:
  • Send events (EMAIL_VERIFICATION_MESSAGE_SENT, EMAIL_VERIFICATION_RETRY_MESSAGE_SENT) — { status, reason }. status is Success, Retry or Undeliverable; reason is null unless the send failed (email_can_not_be_delivered, or unknown for unrecognized legacy values).
  • Check events (VALID_CODE_ENTERED, INVALID_CODE_ENTERED) — { code_tried, status } with status Approved, Failed, Expired or Not Found or Declined.
  • Final status eventsnull, except EMAIL_VERIFICATION_DECLINED / EMAIL_VERIFICATION_IN_REVIEW, which carry { "reason": "<risk code>" } (e.g. UNDELIVERABLE_EMAIL_DETECTED).
Sends with status Success or Undeliverable record the $0.03 Email Verification fee; Retry sends, check events and status events always carry fee: 0.

Cross-session matches

matches[] records the same address on previously approved email verifications in the same application — across KYC, KYB and standalone API sessions — plus blocklist hits configured in the management-api lists. Verifications sharing the current session’s vendor_data are excluded (the same end-user does not match themselves), the array is capped at 5 entries, ordered oldest-first. When the address 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.

Examples

Approved — clean OTP with one informational breach

Declined — undeliverable address

A pre-filled address that fails syntax or DNS (MX) validation — or whose OTP email cannot be delivered — finalizes immediately as Declined. No code-check events appear because no code ever reached the user.

Declined — blocklisted disposable mailbox

The user completed the OTP, but the address matched your email blocklist (auto-decline). The disposable flag is also raised — at information level here because disposable_email_action is left at its default NO_ACTION.