--- title: Sendblue Verify | Sendblue Docs description: 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`. ## How it works 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. ## Base URL ``` https://api.sendblue.com ``` All Verify endpoints are under: ``` /api/v2/verify ``` ## Authentication 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" ``` ## Services 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`. ### Create a service ``` POST /api/v2/verify/services ``` #### Request body | Parameter | Type | Required | Description | | -------------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `friendly_name` | string | Yes | Human-readable name for the service. | | `code_length` | integer | No | Verification code length, from 4 to 8 characters. Default: `6`. | | `ttl_seconds` | integer | No | How long a verification remains pending, from 60 to 3600 seconds. Default: `300`. | | `use_customer_lines` | boolean | No | If 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 } ``` #### Example request 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 }' ``` #### Success response (201) ``` { "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" } ``` ### List services ``` GET /api/v2/verify/services ``` #### Example request 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" ``` #### Success response (200) ``` { "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 } } ``` ### Retrieve a service ``` GET /api/v2/verify/services/:service_sid ``` #### Example request 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" ``` ### Update a service ``` POST /api/v2/verify/services/:service_sid ``` Only include fields you want to change. #### Example request 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 a service ``` DELETE /api/v2/verify/services/:service_sid ``` Deleting a service prevents new verifications from being issued with that service. Existing verification history is retained. #### Example request 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`. ## Verifications A Verification represents one attempt to verify one phone number. Verification SIDs start with `VR`. ### Create a verification ``` POST /api/v2/verify/services/:service_sid/verifications ``` #### Request body | Parameter | Type | Required | Description | | --------- | ------ | -------- | --------------------------------------------- | | `to` | string | Yes | Phone number being verified, in E.164 format. | PascalCase is also accepted: ``` { "To": "+14155551212" } ``` #### Example request 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" }' ``` #### Success response (201) ``` { "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. ### Check verification status ``` GET /api/v2/verify/services/:service_sid/verifications/:verification_sid ``` #### Example request 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" ``` #### Pending response (200) ``` { "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" } } ``` #### Approved response (200) ``` { "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: | Status | Meaning | | ---------- | ------------------------------------------------------------- | | `pending` | Waiting for the user to text the code. | | `approved` | The expected phone number sent the correct code. | | `expired` | The verification was not approved before its expiration time. | | `canceled` | The verification was canceled by Sendblue operational action. | ## Recommended UI flow 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. ``` ## Account-assigned lines 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. ## Webhooks 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. ### Add a Verify webhook 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"] }' ``` #### Success response (200) ``` { "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. ### Replace a Verify webhook 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. ### Webhook events | Event | Description | | ----------------------- | ---------------------------------------------------------------- | | `verification.approved` | The user texted the correct code from the expected phone number. | | `verification.expired` | The verification expired before approval. | | `verification.canceled` | The 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`. ### Webhook signatures If a secret is configured, Sendblue signs each Verify webhook with: ``` X-Sendblue-Signature: t=,v1= ``` 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. ## Rate limits and capacity 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`. ## Error format Verify errors use this error envelope: ``` { "code": 60200, "message": "Invalid request.", "more_info": "https://docs.sendblue.com/errors/60200", "status": 400 } ``` Common errors: | HTTP | Code | Message | Meaning | | ---- | ------- | ------------------------------------------------------- | ----------------------------------------------------------------------- | | 400 | `60200` | Invalid request. | Request body or parameters are invalid. | | 401 | `20003` | Authentication failed. | API credentials are missing or invalid. | | 404 | `60002` | Company not found for credentials. | The API key is not linked to a company. | | 404 | `60201` | Service not found. | Service SID does not exist, was deleted, or belongs to another account. | | 404 | `60223` | Verification not found. | Verification SID does not exist or belongs to another service/account. | | 429 | `60207` | Rate limit exceeded; please retry later. | A protective burst limit was exceeded. | | 503 | `60226` | Verify service pool unavailable; no active pool worker. | No eligible verification line is currently available. | | 503 | `60227` | Verify code generation failed; please retry. | Code collision retries were exhausted. | | 500 | `20500` | Internal server error. | Unexpected server error. | ## End-to-end example 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`. ## Notes - 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.