SMS#

Use the SMS delivery method to send a one-time code in a text message. The user enters the code in your application, and your application reports it to the Verification API.


How it works#

  1. You start a verification with delivery_method set to sms.

  2. The Verification API sends a text message containing a newly generated numeric code to the destination. The code length is set on the OTP application.

  3. The user enters the code in your application.

  4. Your application reports the code to the report endpoint.

  5. If the code matches, the verification status becomes verified.

By default, the message is sent from DIDWW’s shared senders. An OTP application can use dedicated senders from the account’s verified senders, selected in Sender IDs in the SMS Settings section of the application. A sender is verified per destination country. A verification sends from a selected sender that is approved for the destination country, and falls back to DIDWW’s shared senders otherwise. See SMS Settings. To get a sender verified, see Sender ID Verifications.

Starting an SMS verification#

Set delivery_method to sms. Add optional SMS settings in an sms object inside data. The API reads this object only when delivery_method is sms.

The sms object accepts the following options:

  • languages selects the preferred message-template languages. See Supported languages.

  • autofill selects the marker that lets the phone offer or capture the code automatically. See Code autofill.

  • template_group names one of your custom SMS template groups. See Custom templates.

The following example uses HTTP Basic authentication and requests the en-US message template:

http

POST /api/v1/verifications HTTP/1.1
Host: verification.didww.com
Content-Type: application/json
Accept: application/json
Authorization: Basic eW91cl9hcHBfa2V5OnlvdXJfYXBwX3NlY3JldA==

{
  "data": {
    "destination": "+4915112345678",
    "delivery_method": "sms",
    "sms": {
      "languages": ["en-US"]
    }
  }
}

curl

curl -i -X POST https://verification.didww.com/api/v1/verifications -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"data": {"delivery_method": "sms", "destination": "+4915112345678", "sms": {"languages": ["en-US"]}}}' --user your_app_key:your_app_secret

response

HTTP/1.1 201 Created
Content-Type: application/json

{
  "data": {
    "id": "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21",
    "destination": "4915112345678",
    "delivery_method": "sms",
    "fee": "0.06",
    "status": "pending",
    "error_code": null,
    "error_detail": null,
    "expires_at": "2026-07-15T10:05:00.000Z",
    "sms": {
      "template": "Your code is {{CODE}}",
      "language": "en-US",
      "interception_timeout": 300,
      "code_length": 6,
      "template_group": null,
      "template_group_used": false,
      "template_fallback_reason": null
    }
  }
}

This response shows an approved start with status pending. A 201 Created response can also contain status denied, for example when a request callback rejects the verification, the application is paused, or the destination country is blocked, or status failed when DIDWW cannot deliver SMS to the destination. Always inspect status and error_code.

A pending status means the verification was accepted for delivery. It does not confirm that the text message reached the destination.

The response includes an sms object containing SMS-specific information:

  • template contains the message template. {{CODE}} marks where the generated code appears.

  • language is the language the API selected, which is not necessarily the first one requested. Compare it with the list you sent to detect a fallback to en-US.

  • interception_timeout specifies, in seconds, how long an on-device SMS listener should remain active. It equals the verification lifetime set by the OTP application’s Code timeout. It does not control verification expiry. Manual code entry remains available until expires_at.

  • code_length is the number of digits in the code sent for this verification, from 4 to 8. Use it to size the code input field.

  • autofill describes the autofill marker added to the message, with type (app_hash or domain) and value (the app hash or the SMS domain). It is omitted when the message has no marker. app_hash is also returned, for type app_hash only; it is deprecated, so read autofill.value instead.

  • template_group, template_group_used, and template_fallback_reason show whether a custom template group produced the message. See Custom templates.

Supported languages#

Pass sms.languages as BCP 47 tags, most preferred first. The first supported tag wins; if none of them is supported, the message is sent in en-US.

Tags are matched exactly, so the region subtag is required: pl does not match pl-PL and falls back to en-US. A tag that is not a well-formed BCP 47 tag is rejected with languages_invalid instead.

Read sms.language from the response to see which language was actually used.

The following language tags are supported:

BCP 47 tag

Language

bg-BG

Bulgarian (Bulgaria)

bs-BA

Bosnian (Bosnia and Herzegovina)

cs-CZ

Czech (Czechia)

da-DK

Danish (Denmark)

de-DE

German (Germany)

el-GR

Greek (Greece)

en-GB

English (United Kingdom)

en-US

English (United States)

es-419

Spanish (Latin America)

es-ES

Spanish (Spain)

et-EE

Estonian (Estonia)

fi-FI

Finnish (Finland)

fr-FR

French (France)

he-IL

Hebrew (Israel)

hr-HR

Croatian (Croatia)

hu-HU

Hungarian (Hungary)

is-IS

Icelandic (Iceland)

it-IT

Italian (Italy)

ja-JP

Japanese (Japan)

ka-GE

Georgian (Georgia)

lt-LT

Lithuanian (Lithuania)

lv-LV

Latvian (Latvia)

mk-MK

Macedonian (North Macedonia)

ms-MY

Malay (Malaysia)

mt-MT

Maltese (Malta)

nb-NO

Norwegian Bokmål (Norway)

nl-NL

Dutch (Netherlands)

pl-PL

Polish (Poland)

pt-BR

Portuguese (Brazil)

pt-PT

Portuguese (Portugal)

ro-RO

Romanian (Romania)

ru-RU

Russian (Russia)

sk-SK

Slovak (Slovakia)

sl-SI

Slovenian (Slovenia)

sq-AL

Albanian (Albania)

sr-RS

Serbian (Serbia)

sv-SE

Swedish (Sweden)

th-TH

Thai (Thailand)

uk-UA

Ukrainian (Ukraine)

zh-CN

Chinese Simplified (China)

zh-HK

Chinese Traditional (Hong Kong)

Code autofill#

An autofill marker lets the phone offer the code to the user, or lets your application read it, so the user does not type it. Choose the marker with sms.autofill.type:

type

Effect

domain

Appends a last line @<domain> #<code> with the iOS SMS domain of the OTP application. iOS and Safari offer the code above the keyboard, and browsers that support the WebOTP API can read it. See Domain-bound SMS.

app_hash

Formats the message for the Android SMS Retriever API. Pass your application’s hash in value. See Android SMS Retriever.

none

Adds no marker. The message contains only the template text.

When you omit autofill, or send null, the OTP application decides: messages get the domain marker when the application has an iOS SMS domain, and no marker otherwise.

The following example requests the Android marker:

{
  "data": {
    "destination": "+4915112345678",
    "delivery_method": "sms",
    "sms": {
      "autofill": {
        "type": "app_hash",
        "value": "FA+9qCX9VSu"
      }
    }
  }
}

value is required for app_hash and must not be sent for domain or none. The API rejects the request with 422 in the following cases:

  • autofill_invalid: the autofill object is malformed, or is sent together with the deprecated sms.app_hash.

  • app_hash_invalid: the app hash is not exactly 11 characters from A-Z, a-z, 0-9, +, and /.

  • sms_domain_not_configured: type is domain but the OTP application has no iOS SMS domain.

See Errors and status codes.

Note

The flat sms.app_hash field is deprecated but still accepted. It is read only when autofill is omitted, and works like autofill with type app_hash.

Domain-bound SMS#

A domain-bound SMS ends with a line that names your website’s domain and repeats the code. For an application whose iOS SMS domain is example.com, the message looks like this:

Your code is 123456

@example.com #123456

The phone offers the code only on that domain, and in iOS applications associated with it. This helps protect users from phishing sites that ask them to type a code meant for your site.

To send domain-bound SMS:

  1. Set the iOS SMS domain of the OTP application. See Set the iOS SMS domain.

  2. For iOS applications, add the domain to the application’s Associated Domains as webcredentials:<domain>, and publish an apple-app-site-association file on the domain. DIDWW checks only the domain syntax; ownership is proven by that file.

  3. Start verifications as usual. Every SMS of the application gets the domain line by default.

To send a message without the domain line, for example to an iOS application that has no Associated Domains entry, pass "autofill": {"type": "none"}. A request that uses the Android app_hash marker never gets the domain line.

Important

The domain line counts toward the message length. With a long domain, a message that fits one SMS segment without the line can be split into several segments.

Android SMS Retriever#

With type app_hash, the message starts with <#> and ends with the 11-character hash of your Android application, as the SMS Retriever API expects:

<#> Your code is 123456 FA+9qCX9VSu

An Android application that listens through SMS Retriever receives the message without asking for the SMS permission. Keep the listener active for sms.interception_timeout seconds.

Custom templates#

By default, the message text comes from DIDWW’s system templates. To send your own text, create an SMS template group in the User Panel and get its language versions approved. See Custom templates.

A verification uses the group named in sms.template_group, or, when the request names none, the application’s default SMS template group.

{
  "data": {
    "destination": "+4915112345678",
    "delivery_method": "sms",
    "sms": {
      "template_group": "acme-login",
      "languages": ["de-DE", "en-US"]
    }
  }
}

A name that does not match one of your SMS template groups is rejected with 422 template_group_not_found. Any other problem with the group does not fail the request: the message is sent with the system template, and the response says why.

template_group, template_group_used, and template_fallback_reason in the sms object report the result. See How the API selects a template. When the group is used, sms.template contains your text and sms.language the language of the version that was sent.

Reporting the code#

Submit the code the user received. The delivery_method must match the verification. The following example reports a correct code and returns status verified:

http

PATCH /api/v1/verifications/0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21 HTTP/1.1
Host: verification.didww.com
Content-Type: application/json
Accept: application/json
Authorization: Basic eW91cl9hcHBfa2V5OnlvdXJfYXBwX3NlY3JldA==

{
  "data": {
    "delivery_method": "sms",
    "code": "123456"
  }
}

curl

curl -i -X PATCH https://verification.didww.com/api/v1/verifications/0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21 -H "Accept: application/json" -H "Content-Type: application/json" --data-raw '{"data": {"code": "123456", "delivery_method": "sms"}}' --user your_app_key:your_app_secret

response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "id": "0f9c8b7a-1e2d-4c3b-9a8f-7e6d5c4b3a21",
    "destination": "4915112345678",
    "delivery_method": "sms",
    "fee": "0.06",
    "status": "verified",
    "error_code": null,
    "error_detail": null,
    "expires_at": "2026-07-15T10:05:00.000Z",
    "sms": {
      "template": "Your code is {{CODE}}",
      "language": "en-US",
      "interception_timeout": 300,
      "code_length": 6,
      "template_group": null,
      "template_group_used": false,
      "template_fallback_reason": null
    }
  }
}

The response above shows a successful report. The API returns 200 OK with status verified when the submitted code matches.

If the submitted code is incorrect, the API returns the following response:

HTTP/1.1 422 Unprocessable Content
Content-Type: application/json

{
  "errors": [
    {
      "code": "code_invalid",
      "detail": "code is invalid"
    }
  ]
}

After an incorrect report, the verification remains pending, and the user can submit another code until the attempt limit is reached. After three unsuccessful reports, the verification becomes failed with error_code too_many_attempts.

Next steps#