curl --request POST \
--url https://api.contiguity.com/otp/reverse/initiate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"number": "+15551234567",
"to": "+16465550123",
"service": "sms",
"language": "en",
"success_url": "https://example.com/webhooks/otp/success"
}
'import requests
url = "https://api.contiguity.com/otp/reverse/initiate"
payload = {
"number": "+15551234567",
"to": "+16465550123",
"service": "sms",
"language": "en",
"success_url": "https://example.com/webhooks/otp/success"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
number: '+15551234567',
to: '+16465550123',
service: 'sms',
language: 'en',
success_url: 'https://example.com/webhooks/otp/success'
})
};
fetch('https://api.contiguity.com/otp/reverse/initiate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.contiguity.com/otp/reverse/initiate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'number' => '+15551234567',
'to' => '+16465550123',
'service' => 'sms',
'language' => 'en',
'success_url' => 'https://example.com/webhooks/otp/success'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.contiguity.com/otp/reverse/initiate"
payload := strings.NewReader("{\n \"number\": \"+15551234567\",\n \"to\": \"+16465550123\",\n \"service\": \"sms\",\n \"language\": \"en\",\n \"success_url\": \"https://example.com/webhooks/otp/success\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.contiguity.com/otp/reverse/initiate")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"number\": \"+15551234567\",\n \"to\": \"+16465550123\",\n \"service\": \"sms\",\n \"language\": \"en\",\n \"success_url\": \"https://example.com/webhooks/otp/success\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.contiguity.com/otp/reverse/initiate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"number\": \"+15551234567\",\n \"to\": \"+16465550123\",\n \"service\": \"sms\",\n \"language\": \"en\",\n \"success_url\": \"https://example.com/webhooks/otp/success\"\n}"
response = http.request(request)
puts response.read_body{
"id": "req_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "response",
"data": {
"otp_id": "r_otp_123456",
"number": "+15551234567",
"factor": "ABC123",
"to": "+16465550123",
"service": "sms",
"expires_at": "2025-02-01T12:00:00.000Z",
"ui": {
"text": "Text ABC123 to +1 (646) 555-0123 to verify your phone number.",
"qr_code": "sms:+16465550123?body=ABC123"
},
"success_url": "https://example.com/webhooks/otp/success"
}
}{
"id": "err_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "error",
"data": {
"error": "Bad Request",
"status": 400
}
}{
"id": "err_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "error",
"data": {
"error": "Unauthorized",
"status": 401
}
}{
"id": "err_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "error",
"data": {
"error": "Forbidden",
"status": 403
}
}{
"id": "err_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "error",
"data": {
"error": "Internal Server Error",
"status": 500
}
}Initiate a Reverse OTP session
curl --request POST \
--url https://api.contiguity.com/otp/reverse/initiate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"number": "+15551234567",
"to": "+16465550123",
"service": "sms",
"language": "en",
"success_url": "https://example.com/webhooks/otp/success"
}
'import requests
url = "https://api.contiguity.com/otp/reverse/initiate"
payload = {
"number": "+15551234567",
"to": "+16465550123",
"service": "sms",
"language": "en",
"success_url": "https://example.com/webhooks/otp/success"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
number: '+15551234567',
to: '+16465550123',
service: 'sms',
language: 'en',
success_url: 'https://example.com/webhooks/otp/success'
})
};
fetch('https://api.contiguity.com/otp/reverse/initiate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.contiguity.com/otp/reverse/initiate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'number' => '+15551234567',
'to' => '+16465550123',
'service' => 'sms',
'language' => 'en',
'success_url' => 'https://example.com/webhooks/otp/success'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.contiguity.com/otp/reverse/initiate"
payload := strings.NewReader("{\n \"number\": \"+15551234567\",\n \"to\": \"+16465550123\",\n \"service\": \"sms\",\n \"language\": \"en\",\n \"success_url\": \"https://example.com/webhooks/otp/success\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.contiguity.com/otp/reverse/initiate")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"number\": \"+15551234567\",\n \"to\": \"+16465550123\",\n \"service\": \"sms\",\n \"language\": \"en\",\n \"success_url\": \"https://example.com/webhooks/otp/success\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.contiguity.com/otp/reverse/initiate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"number\": \"+15551234567\",\n \"to\": \"+16465550123\",\n \"service\": \"sms\",\n \"language\": \"en\",\n \"success_url\": \"https://example.com/webhooks/otp/success\"\n}"
response = http.request(request)
puts response.read_body{
"id": "req_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "response",
"data": {
"otp_id": "r_otp_123456",
"number": "+15551234567",
"factor": "ABC123",
"to": "+16465550123",
"service": "sms",
"expires_at": "2025-02-01T12:00:00.000Z",
"ui": {
"text": "Text ABC123 to +1 (646) 555-0123 to verify your phone number.",
"qr_code": "sms:+16465550123?body=ABC123"
},
"success_url": "https://example.com/webhooks/otp/success"
}
}{
"id": "err_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "error",
"data": {
"error": "Bad Request",
"status": 400
}
}{
"id": "err_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "error",
"data": {
"error": "Unauthorized",
"status": 401
}
}{
"id": "err_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "error",
"data": {
"error": "Forbidden",
"status": 403
}
}{
"id": "err_xxxxxxxxxxxxxxxx",
"timestamp": 1787255833552,
"api_version": "v2026.8.20",
"object": "error",
"data": {
"error": "Internal Server Error",
"status": 500
}
}What is a Reverse OTP?
A reverse OTP verifies a user’s phone number by having the user send a code to you via SMS or iMessage, rather than you sending it to them. This approach offers several key benefits compared to traditional OTPs:- Receiving SMS messages is easy and often free, so attackers may abuse online services to receive codes and bypass standard verification.
- Sending an SMS, however, requires the user to use their own phone and carrier plan, making it more difficult to fake and providing greater confidence in the user’s authenticity.
- You save on operational costs, since you’re not paying to send SMS codes to your users.
- Message delivery rates are often higher. Outbound SMS can be blocked, delayed, or filtered by carriers—especially for international users or high-volume senders. When users initiate the message themselves, it is much more likely to be delivered, as their carrier typically supports sending SMS to the US from anywhere in the world.
service to "sms" or "imessage". It defaults to "sms" when omitted. iMessage requires an active iMessage-capable leased number and an iMessage entitlement. Learn more about Reverse OTP with iMessage.const session = await contiguity.otp.reverse.initiate({
language: "en",
service: "sms"
});
number to require that exact sender for a successful verification:
const session = await contiguity.otp.reverse.initiate({
number: "+15551234567",
language: "en",
service: "sms"
});
session.ui.qr_code. Do not hardcode an sms: URL.await generateQRCode(session.ui.qr_code);
Result
qr_code payload can also be placed into a button or anchor tag’s href attribute, which will automatically open the user’s messaging app with the phone number and body fields populated!<!-- add styling to the button -->
<a href={session.ui.qr_code}>Tap to Verify</a>
<!-- session.ui.qr_code is a string like "sms:+15551234567?body=ABC123">

Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Optional. Expected sender phone number in E.164. If omitted, Contiguity learns the number from the inbound message — the user never has to type it.
"+15551234567"
Phone number to text. If you lease a number from Contiguity, you can enter it here. Defaults to a random Contiguity-owned number.
"+16465550123"
Messaging service to receive the code on. Defaults to SMS. iMessage requires an active iMessage-capable number on your account.
sms, imessage "sms"
Language code for UI strings
"en"
Override the default webhook URL for otp.reverse.verified. By default, Contiguity will use your existing webhook preferences.
"https://example.com/webhooks/otp/success"
Response
Start a Reverse OTP verification session. Pass number only if you already know the sender and want to require that exact phone. Omit it to discover the number from the inbound message.
Was this page helpful?