Skip to main content
Version: Latest

Phone Number Verifications

Feature NamePhone Number Verifications
Feature IDCrestApps.OrchardCore.PhoneNumbers.Verifications

The Phone Number Verifications module provides a provider-agnostic framework for verifying phone numbers and storing the results directly on content items through a content part. It manages verification providers, content-part storage helpers, SQL indexing, and a background revalidation process. When the shared Reports module is enabled, this module also contributes its report automatically.

The core feature does not depend on any external verification provider. Providers ship as separate features (for example, AbstractAPI Phone Number Verification, Veriphone Phone Number Verification, and Twilio Phone Number Verification) and are discovered dynamically, so adding a provider never requires changes to the core feature.

The core feature is enabled on demand. It is activated automatically when a dependent feature (such as a provider feature) is enabled, or you can enable it directly under Configuration -> Features.

Architecture

The framework is built from a small set of cooperating abstractions:

AbstractionResponsibility
IPhoneNumberVerificationProviderCalls an external verification service and maps the response into the common result model. Registered against a unique provider key.
IPhoneNumberVerificationManagerSelects the active provider, resolves providers by key, executes verification, and raises verification lifecycle handlers.
PhoneNumberVerificationPartExtensionsReads and updates verification data stored on content items with contentItem.Alter<PhoneNumberVerificationPart>(...) and contentItem.TryGet<PhoneNumberVerificationPart>(...).
IPhoneNumberVerificationHandlerReceives Verifying and Verified lifecycle events.
PhoneNumberVerificationResultThe provider-agnostic result model. Providers map their native responses into this shape and may extend it through the Metadata bag.

Providers are resolved by a registered provider key; a provider never selects itself. Provider keys, display names, and descriptions are registered as metadata and discovered dynamically by the provider selection setting.

Features

The module ships with the following features:

FeatureFeature IDDescription
Phone Number VerificationsCrestApps.OrchardCore.PhoneNumbers.VerificationsThe core framework, settings, content part, SQL index, automatic contact verification, background revalidation, and automatic integration with the shared Reports area when CrestApps.OrchardCore.Reports is enabled.
AbstractAPI Phone Number VerificationCrestApps.OrchardCore.PhoneNumbers.Verifications.AbstractApiVerifies phone numbers using the AbstractAPI Phone Validation service.
Veriphone Phone Number VerificationCrestApps.OrchardCore.PhoneNumbers.Verifications.VeriphoneVerifies phone numbers using the Veriphone phone number validation API.
Twilio Phone Number VerificationCrestApps.OrchardCore.PhoneNumbers.Verifications.TwilioVerifies phone numbers using the Twilio Lookup API.

Enable a provider feature to activate the core feature and make the provider available for selection.

Verification result model

Providers return a provider-agnostic PhoneNumberVerificationResult:

FieldDescription
PhoneNumberThe phone number that was submitted.
NormalizedPhoneNumberThe number in E.164 format.
IsValid / IsReachableWhether the number is valid and reachable.
IsMobile / IsLandline / IsVoipLine-type flags.
NationalFormatThe provider-supplied national display format, when available.
CountryCode / CountryName / CountryPrefixCountry information.
Region / CityProvider-reported location details.
CarrierThe carrier name.
TimeZoneThe IANA time zone identifier.
LineTypeThe normalized line type.
LineStatusThe provider-specific line status, when available.
MinimumAgeThe provider-specific minimum observed line age, when available.
RiskScore / RiskLevelOptional provider risk information.
IsDisposable / IsAbuseDetectedOptional provider risk flags.
VerificationProviderThe provider key that produced the result.
ProviderReferenceIdThe provider-specific reference identifier.
VerificationDateUtcWhen the verification was performed.
RawProviderResponseThe raw, unmodified provider response.
StatusThe normalized status (Unverified, Verified, Invalid, Failed).
MetadataA provider-extensible bag for additional values.

The entire normalized response is stored, so future providers can expose additional information without schema changes. Rich responses such as Dialpad Professional phone intelligence can map common fields (format, carrier, location, validation, and risk) into the shared model while retaining plan-specific details such as messaging, registration, and breach data in Metadata and RawProviderResponse.

Content item integration

Verification data is stored on contact content items through the Phone Number Verification content part (PhoneNumberVerificationPart). Attach the part to any content type that represents a contact under Content -> Content Definition -> Content Types.

The part stores:

FieldDescription
PhoneNumberThe phone number submitted for verification.
NormalizedPhoneNumberThe normalized phone number in E.164 format when available.
LastVerifiedUtcThe UTC timestamp of the most recent completed validity verification.
LastVerifiedByUserIdThe identifier of the user who last triggered a verification. User identifiers are stored instead of usernames because usernames may change over time.
VerificationProviderThe provider key that produced the stored result.
VerificationStatusThe normalized verification status.
VerificationResultJsonThe full normalized provider response.
VerificationAttemptCountThe number of verification attempts.
FailedAttemptCountThe number of consecutive failed verification requests (provider or transport errors). Reset to zero when a verification request completes successfully or the record is re-queued.
LastErrorThe error message from the most recent failed verification request, when any.
LastAttemptUtcThe UTC timestamp of the most recent verification attempt, whether it completed or failed.
NextVerificationDueUtcWhen the next verification becomes due.

SQL index

The module maintains a PhoneNumberVerificationPartIndex SQL index over the commonly queried fields (content item id, raw and normalized phone numbers, verification status, provider, last-verified and next-due timestamps, country code, carrier, line-type flags, and line status). The index powers reporting, dashboard widgets, revalidation jobs, and administrative searches. Provider-specific metadata stays in the stored JSON payload and is not indexed.

Verification workflow

Verification happens through one of three paths:

  1. Automatic contact changes — when the Omnichannel Management feature is enabled, a content handler watches omnichannel contact content items. On create or update, it extracts the preferred phone number from the ContactMethods bag (Cell, then Home, Office, Work, Other) and verifies it when it is new or different from the number stored in PhoneNumberVerificationPart.
  2. Background revalidation — a scheduled job verifies contacts that already have a stored phone number and are due.
  3. Explicit requests — a verification is triggered for a specific phone number.

Automatic verification first stores the changed number as Unverified on the contact and preferred phone-number record during the current content save. The external provider call then runs as deferred work after that save completes, so the provider result is the only later update to the verification status. If no provider is enabled, the number remains pending and the background revalidation task can pick it up later when a provider becomes available.

When a phone field is rendered for display or editing, the UI shows a status icon next to the number when verification data is available on the same content item. Verified numbers show a green check mark, invalid numbers show a red error icon, failed verifications show a warning icon, and unverified numbers show a muted unknown icon. Each icon includes a tooltip that explains the status and includes the last verification timestamp when one is available.

Resilience and retries

The framework distinguishes a completed verification (the provider returned a definitive Verified or Invalid answer) from a failed request (a provider rate limit, HTTP error, transport failure, or unparseable response). A failed request never marks a number as Invalid, because the number's validity was never actually determined. Built-in provider HTTP clients are created through IHttpClientFactory and registered with the standard .NET HTTP resilience handler so transient network and service failures are retried consistently.

When a verification request fails:

  • FailedAttemptCount is incremented and the provider error message is stored in LastError.
  • A record that has never completed verification is surfaced with the Failed status and is kept due so the background task retries it.
  • A record that was previously verified is left untouched (status, LastVerifiedUtc, and NextVerificationDueUtc are preserved), so a transient provider outage never downgrades a known-good number. Only FailedAttemptCount and LastError are updated.

Failed records are retried automatically by the background task until FailedAttemptCount reaches the configured Maximum verification attempts (default 3). Once a record reaches that cap it stops auto-retrying and is flagged as Needs attention in the records queue, where an administrator can inspect the error and manually retry it. When a verification request finally completes, FailedAttemptCount and LastError are reset to zero/null.

Phone Verifications Queue

A Phone Verifications Queue dashboard is available under Tools for users who have the RunPhoneNumberVerificationsReport permission. It lists every content item carrying verification data and lets administrators:

  • see clickable status tiles (All, Verified, Invalid, Failed, Pending, and Needs attention) that show per-status counts and filter the list when selected. The status buckets are mutually exclusive and always sum to the total: Pending counts records awaiting verification (unverified status, including records just re-queued), Failed counts records whose last request failed but can still be retried automatically, and Needs attention counts records that have reached the maximum failed attempts,
  • search records by raw or normalized phone number,
  • sort records by most or least recently attempted, or newest or oldest created,
  • review each record's phone number, status, provider, line status, minimum age, total and failed attempt counts, and last attempt timestamp as compact tags, with the most recent provider error rendered as a red code-style message,
  • page through large result sets,
  • re-queue a single record with Retry now, re-queue the selected records with Retry selected (use the Select all on this page checkbox to select every row on the current page), or re-queue every failed or needs-attention record across all pages that matches the current search with Retry all failed (requires the VerifyPhoneNumbers permission).

All retry actions are queued, not synchronous: they reset the affected records' failure counters and mark them Pending immediately, then deferred verification work runs after the pending state is saved. The scheduled background task remains a safety net for any records still due later. This keeps the page responsive even when re-queuing many records and lets the throttle space out provider calls to avoid rate limits (HTTP 429).

Explicit callers are responsible for providing the phone number to verify. After a provider returns a PhoneNumberVerificationResult, store it on the content item with contentItem.AlterPhoneNumberVerificationResult(result, verifiedByUserId, revalidationIntervalDays). Consumers can check for existing data with contentItem.TryGet<PhoneNumberVerificationPart>(out var part) or read the stored result with contentItem.TryGetPhoneNumberVerificationResult(out var result).

var result = await verificationManager.VerifyAsync(phoneNumber, cancellationToken: cancellationToken);

contentItem.AlterPhoneNumberVerificationResult(
result,
verifiedByUserId: userId,
revalidationIntervalDays: settings.RevalidationIntervalDays);

Background revalidation

A throttled background task runs every five minutes, finds content items that already carry a stored phone number and whose verification is due (including records that remain pending after a queue retry), verifies them in resilient batches, and updates the stored results, the SQL index, and reporting data. The task:

  • processes work in bounded, throttled batches to scale to large data sets without overlapping long-running provider calls
  • throttles consecutive provider calls using the configured Request delay (milliseconds) setting to respect provider rate limits (HTTP 429)
  • tolerates provider failures without stopping the run
  • uses distributed locking so it is safe to run across multiple instances

Caching and cost optimization

External verification APIs are paid services, so the framework minimizes provider calls. Verification data stored on PhoneNumberVerificationPart acts as the authoritative cache. A number is only (re)verified when its stored verification has expired or a caller explicitly requests revalidation. Cached results are always read from the content item first.

Reporting

Enable Reports (CrestApps.OrchardCore.Reports) alongside Phone Number Verifications to surface the report directly under Reports for users who have the RunPhoneNumberVerificationsReport permission. The report uses the shared Reports module renderer and export pipeline, and it surfaces operational metrics such as total contacts, verified and unverified numbers, invalid numbers, mobile/landline/VoIP counts, numbers pending verification, numbers requiring revalidation, verification success rate, verification failures, and provider usage counts. The reporting infrastructure is built on the SQL index and is extensible for future dashboard widgets.

Phone number verifications report dashboard

Screenshot placeholder: the report dashboard.

Configuration

Configure the module under Settings -> Phone Number Verifications.

SettingDefaultPurpose
Default providerFirst availableThe provider used by default. The selector lists only enabled providers. If no provider matches the selection (or none is chosen), the first enabled provider is used.
Revalidation interval (days)365The number of days after which a verified number must be revalidated.
Maximum verification attempts3The maximum number of consecutive failed verification requests before a record stops auto-retrying and is flagged as Needs attention in the records queue.
Request delay (milliseconds)1000The delay between consecutive provider requests during background processing. Increase this value to space out calls and avoid provider rate limits (HTTP 429) when many records are verified in sequence.

Phone number verifications core settings

Screenshot placeholder: the core settings page.

Each provider feature contributes its own tab to the same settings page, following the Orchard Core SMS module pattern. Provider tabs only appear when the provider feature is enabled.

Each provider tab includes an Enable this provider switch. A provider is only used for verification and only appears in the Default provider selector when this switch is on. Turning the switch on reveals the provider's connection and authentication fields, which are then validated when the settings are saved; turning it off hides those fields and skips their validation. If you disable the provider that is currently selected as the default, the default selection is cleared and the framework falls back to the first enabled provider.

Provider settings tab

Screenshot placeholder: a provider settings tab.

Extensibility

Adding a provider never requires changes to the core feature. A provider only needs to:

  1. Create a new feature.
  2. Implement IPhoneNumberVerificationProvider.
  3. Register provider-specific settings (optional).
  4. Register the implementation and its dependencies.
  5. Register the provider key and metadata.

Creating a custom provider

Reference CrestApps.OrchardCore.PhoneNumbers.Abstractions, then implement IPhoneNumberVerificationProvider from the CrestApps.OrchardCore.PhoneNumbers namespace and map the external response into PhoneNumberVerificationResult:

public sealed class MyPhoneNumberVerificationProvider : IPhoneNumberVerificationProvider
{
public async Task<PhoneNumberVerificationResult> VerifyAsync(
string phoneNumber,
CancellationToken cancellationToken = default)
{
// Call the external service and map the response.
return new PhoneNumberVerificationResult
{
PhoneNumber = phoneNumber,
NormalizedPhoneNumber = phoneNumber,
IsValid = true,
Status = PhoneNumberVerificationStatus.Verified,
VerificationProvider = "MyProvider",
};
}
}

Register the provider in a feature Startup using the provider key and localized metadata:

[Feature("MyCompany.MyModule.MyProvider")]
public sealed class MyProviderStartup : StartupBase
{
internal readonly IStringLocalizer S;

public MyProviderStartup(IStringLocalizer<MyProviderStartup> stringLocalizer)
{
S = stringLocalizer;
}

public override void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient(nameof(MyPhoneNumberVerificationProvider))
.AddStandardResilienceHandler();

services.AddPhoneNumberVerificationProvider<MyPhoneNumberVerificationProvider>(
"MyProvider",
options =>
{
options.DisplayName = S["My Provider"];
options.Description = S["Verifies phone numbers using My Provider."];
});

// Optional: register a settings display driver for the provider tab.
services.AddSiteDisplayDriver<MyProviderSettingsDisplayDriver>();
}
}

AddPhoneNumberVerificationProvider registers the implementation as a keyed service under the provider key and adds its descriptor so the provider selection setting discovers it automatically. The registration action configures localized provider metadata without allowing the provider key to drift from the keyed service registration.