Skip to content
Get Started
API v2

Sendblue Verify

Verify phone numbers by asking users to text a code to a Sendblue number

Sendblue Verify lets you confirm that a user can send messages from a phone number. Your app shows the user a short code and a Sendblue number. The user texts the code to that number. When Sendblue receives the reply, the verification is marked approved.

  1. Create a Verify Service.
  2. Create a Verification for the phone number you want to verify.
  3. Show the returned code and destination number to your user.
  4. The user texts the code to the destination number.
  5. Poll the Verification status or receive a webhook when it is approved.

This is an inverted-OTP flow: Sendblue does not send the first OTP message to the user. The user sends the code back to Sendblue.

https://api.sendblue.com

All Verify endpoints are under:

/api/v2/verify

Verify uses your Sendblue API credentials. Include your API key and secret in the standard Sendblue request headers:

sb-api-key-id: YOUR_API_KEY
sb-api-secret-key: YOUR_API_SECRET

Examples in this guide use those headers:

Terminal window
curl https://api.sendblue.com/api/v2/verify/services \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET"

A Verify Service stores configuration for a group of verifications, such as code length, expiration window, and whether your account-assigned lines should be used for verification.

Service SIDs start with SV.

POST /api/v2/verify/services
ParameterTypeRequiredDescription
friendly_namestringYesHuman-readable name for the service.
code_lengthintegerNoVerification code length, from 4 to 8 characters. Default: 6.
ttl_secondsintegerNoHow long a verification remains pending, from 60 to 3600 seconds. Default: 300.
use_customer_linesbooleanNoIf true, Sendblue first uses phone numbers already assigned to your account for verification, then falls back to the shared Verify pool. Default: false.

PascalCase keys are also accepted:

{
"FriendlyName": "Production login verify",
"CodeLength": 6,
"TtlSeconds": 300,
"UseCustomerLines": false
}
Terminal window
curl -X POST https://api.sendblue.com/api/v2/verify/services \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"friendly_name": "Production login verify",
"code_length": 6,
"ttl_seconds": 300
}'
{
"sid": "SV7a634393b19c141ac07520602f0e38b6",
"account_sid": "c6812cd8-dd8f-4558-87db-cc41...",
"friendly_name": "Production login verify",
"code_length": 6,
"ttl_seconds": 300,
"use_customer_lines": false,
"date_created": "2026-06-26T18:00:00.000Z",
"date_updated": "2026-06-26T18:00:00.000Z",
"url": "https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6"
}
GET /api/v2/verify/services
Terminal window
curl https://api.sendblue.com/api/v2/verify/services \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET"
{
"services": [
{
"sid": "SV7a634393b19c141ac07520602f0e38b6",
"account_sid": "c6812cd8-dd8f-4558-87db-cc41...",
"friendly_name": "Production login verify",
"code_length": 6,
"ttl_seconds": 300,
"use_customer_lines": false,
"date_created": "2026-06-26T18:00:00.000Z",
"date_updated": "2026-06-26T18:00:00.000Z",
"url": "https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6"
}
],
"meta": {
"page": 0,
"page_size": 1,
"first_page_url": "https://api.sendblue.com/api/v2/verify/services",
"previous_page_url": null,
"next_page_url": null
}
}
GET /api/v2/verify/services/:service_sid
Terminal window
curl https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6 \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET"
POST /api/v2/verify/services/:service_sid

Only include fields you want to change.

Terminal window
curl -X POST https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6 \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"ttl_seconds": 600,
"use_customer_lines": true
}'

Updates apply to future verifications. Existing pending verifications keep the settings they were issued with.

DELETE /api/v2/verify/services/:service_sid

Deleting a service prevents new verifications from being issued with that service. Existing verification history is retained.

Terminal window
curl -X DELETE https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6 \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET"

Success returns 204 No Content.

A Verification represents one attempt to verify one phone number.

Verification SIDs start with VR.

POST /api/v2/verify/services/:service_sid/verifications
ParameterTypeRequiredDescription
tostringYesPhone number being verified, in E.164 format.

PascalCase is also accepted:

{
"To": "+14155551212"
}
Terminal window
curl -X POST https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6/verifications \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"to": "+14155551212"
}'
{
"sid": "VR51d9c7dee89c6d7627c318a773bef7e9",
"service_sid": "SV7a634393b19c141ac07520602f0e38b6",
"account_sid": "c6812cd8-dd8f-4558-87db-cc41...",
"to": "+14155551212",
"channel": "imessage",
"status": "pending",
"date_created": "2026-06-26T18:05:00.000Z",
"date_updated": "2026-06-26T18:05:00.000Z",
"url": "https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6/verifications/VR51d9c7dee89c6d7627c318a773bef7e9",
"delivery_target": {
"pool_number": "+17865640246",
"code": "CB6DJ9",
"sms_deep_link": "sms:+17865640246?body=CB6DJ9"
}
}

Show the user:

  • the code
  • the pool_number
  • optionally, a button or QR code using sms_deep_link

The user should send the code from the phone number in to. A code sent from a different phone number will not approve the verification.

GET /api/v2/verify/services/:service_sid/verifications/:verification_sid
Terminal window
curl https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6/verifications/VR51d9c7dee89c6d7627c318a773bef7e9 \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET"
{
"sid": "VR51d9c7dee89c6d7627c318a773bef7e9",
"service_sid": "SV7a634393b19c141ac07520602f0e38b6",
"account_sid": "c6812cd8-dd8f-4558-87db-cc41...",
"to": "+14155551212",
"channel": "imessage",
"status": "pending",
"date_created": "2026-06-26T18:05:00.000Z",
"date_updated": "2026-06-26T18:05:00.000Z",
"url": "https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6/verifications/VR51d9c7dee89c6d7627c318a773bef7e9",
"delivery_target": {
"pool_number": "+17865640246",
"code": "CB6DJ9",
"sms_deep_link": "sms:+17865640246?body=CB6DJ9"
}
}
{
"sid": "VR51d9c7dee89c6d7627c318a773bef7e9",
"service_sid": "SV7a634393b19c141ac07520602f0e38b6",
"account_sid": "c6812cd8-dd8f-4558-87db-cc41...",
"to": "+14155551212",
"channel": "imessage",
"status": "approved",
"date_created": "2026-06-26T18:05:00.000Z",
"date_updated": "2026-06-26T18:05:24.000Z",
"url": "https://api.sendblue.com/api/v2/verify/services/SV7a634393b19c141ac07520602f0e38b6/verifications/VR51d9c7dee89c6d7627c318a773bef7e9"
}

For recently approved verifications, to is returned while the live verification record is still available. Later terminal-state lookups may return to: null; use the approved webhook if your backend needs the verified phone number asynchronously.

Possible statuses:

StatusMeaning
pendingWaiting for the user to text the code.
approvedThe expected phone number sent the correct code.
expiredThe verification was not approved before its expiration time.
canceledThe verification was canceled by Sendblue operational action.

After creating a Verification:

  1. Display the code clearly.
  2. Display the destination number clearly.
  3. Offer a “Text code” button using delivery_target.sms_deep_link.
  4. Poll the status endpoint every few seconds while the verification is pending.
  5. Stop polling when the status becomes approved, expired, or canceled.
  6. Prefer webhooks for backend systems that need reliable asynchronous updates.

Example copy:

Text CB6DJ9 to +1 (786) 564-0246 from the phone number you want to verify.

By default, Verify uses Sendblue’s shared verification pool.

If your account has assigned verification numbers and your service has use_customer_lines: true, Sendblue first uses your own active numbers for verification. If no assigned number is available, it falls back to the shared pool.

Using account-assigned lines is useful when you want the same lines used for verification to stay available for ongoing conversations with your users.

You can configure a Verify webhook to receive terminal verification events.

Webhook configuration uses the existing Sendblue account webhook API:

POST /api/account/webhooks

Only one Verify webhook URL is supported per account.

Terminal window
curl -X POST https://api.sendblue.com/api/account/webhooks \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"type": "verify",
"webhooks": ["https://example.com/sendblue/verify"]
}'
{
"status": "OK",
"message": "Webhooks added successfully",
"webhooks": {
"receive": [],
"verify": [
{
"url": "https://example.com/sendblue/verify",
"secret": "whsec_..."
}
]
}
}

Store the returned secret. It is used to verify webhook signatures and is also available in the Sendblue dashboard UI in your webhook settings.

Use PUT /api/account/webhooks with a verify array:

Terminal window
curl -X PUT https://api.sendblue.com/api/account/webhooks \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"webhooks": {
"verify": ["https://example.com/sendblue/verify"]
}
}'

If the URL already had a secret, Sendblue preserves it so your signature verification does not break unexpectedly.

EventDescription
verification.approvedThe user texted the correct code from the expected phone number.
verification.expiredThe verification expired before approval.
verification.canceledThe verification was canceled by Sendblue operational action.

Approved payload:

{
"event": "verification.approved",
"verification_id": "VR51d9c7dee89c6d7627c318a773bef7e9",
"phone": "+14155551212",
"completed_at": "2026-06-26T18:05:24.000Z"
}

Expired payload:

{
"event": "verification.expired",
"verification_id": "VR51d9c7dee89c6d7627c318a773bef7e9",
"completed_at": "2026-06-26T18:10:00.000Z"
}

phone is included only for verification.approved.

If a secret is configured, Sendblue signs each Verify webhook with:

X-Sendblue-Signature: t=<unix_timestamp>,v1=<signature>

The signature is:

HMAC_SHA256(secret, `${timestamp}.${raw_request_body}`)

Verify the signature against the raw request body before parsing JSON.

Node.js example:

import crypto from "crypto";
function verifySendblueSignature(rawBody, signatureHeader, secret) {
const parts = Object.fromEntries(
signatureHeader.split(",").map((part) => {
const [key, value] = part.split("=");
return [key, value];
})
);
const timestamp = parts.t;
const signature = parts.v1;
if (!timestamp || !signature) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
if (signature.length !== expected.length) return false;
return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
}

Recommended replay protection: reject signatures whose timestamp is more than 5 minutes old.

Sendblue applies protective limits to keep the Verify system healthy:

  • A short per-account create burst limit protects against accidental loops.
  • A per-line active pending cap spreads load across available Sendblue numbers.
  • Repeated wrong-code replies to the same Sendblue number can temporarily cool that number down.

Valid matching codes are not blocked by the wrong-code cooldown.

If a request is rate-limited, the response includes HTTP 429 and may include a Retry-After header.

If no verification line is currently available, the API returns 503.

Verify errors use this error envelope:

{
"code": 60200,
"message": "Invalid request.",
"more_info": "https://docs.sendblue.com/errors/60200",
"status": 400
}

Common errors:

HTTPCodeMessageMeaning
40060200Invalid request.Request body or parameters are invalid.
40120003Authentication failed.API credentials are missing or invalid.
40460002Company not found for credentials.The API key is not linked to a company.
40460201Service not found.Service SID does not exist, was deleted, or belongs to another account.
40460223Verification not found.Verification SID does not exist or belongs to another service/account.
42960207Rate limit exceeded; please retry later.A protective burst limit was exceeded.
50360226Verify service pool unavailable; no active pool worker.No eligible verification line is currently available.
50360227Verify code generation failed; please retry.Code collision retries were exhausted.
50020500Internal server error.Unexpected server error.

Create a service:

Terminal window
SERVICE=$(curl -s -X POST https://api.sendblue.com/api/v2/verify/services \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"friendly_name":"Login Verify","code_length":6,"ttl_seconds":300}')
SERVICE_SID=$(echo "$SERVICE" | jq -r '.sid')

Issue a verification:

Terminal window
VERIFICATION=$(curl -s -X POST "https://api.sendblue.com/api/v2/verify/services/$SERVICE_SID/verifications" \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"to":"+14155551212"}')
echo "$VERIFICATION" | jq '.delivery_target'

Show the user the returned code and destination number.

Poll status:

Terminal window
VERIFICATION_SID=$(echo "$VERIFICATION" | jq -r '.sid')
curl "https://api.sendblue.com/api/v2/verify/services/$SERVICE_SID/verifications/$VERIFICATION_SID" \
-H "sb-api-key-id: YOUR_API_KEY" \
-H "sb-api-secret-key: YOUR_API_SECRET" | jq .

When the user texts the correct code from the expected phone number, the status changes to approved.

  • The user must text the code from the phone number being verified.
  • The verification code is only valid during the service’s TTL window.
  • Sendblue does not expose a “submit code” endpoint for this flow. Approval happens when Sendblue receives the inbound message from the user.
  • Use webhooks when your backend needs asynchronous notification.
  • Use polling when your frontend needs to update a waiting screen.