Send one SMS from a CRM
Copy page
Use this endpoint when a CRM or automation platform needs one simple webhook that sends one SMS and can safely retry after a timeout.
POST
/api/v1/integrations/sms/send
· Permission: sms.send
Request
Section titled “Request”Send a strict JSON object with three fields and one stable idempotency key:
curl -X POST https://restlink23telecom.com/api/v1/integrations/sms/send \ -H "X-API-Key: $API_KEY" \ -H "Idempotency-Key: crm-event-20260718-001" \ -H "Content-Type: application/json" \ -d '{ "to": "+447700900123", "message": "Your booking is confirmed", "sender_id": "YourBrand"}'const res = await fetch('https://restlink23telecom.com/api/v1/integrations/sms/send', { method: 'POST', headers: { 'X-API-Key': process.env.API_KEY, 'Idempotency-Key': 'crm-event-20260718-001', 'Content-Type': 'application/json' }, body: JSON.stringify({ "to": "+447700900123", "message": "Your booking is confirmed", "sender_id": "YourBrand" }),});
if (!res.ok) { const err = await res.json(); throw new Error(`${err.error_code}: ${err.description}`);}const data = await res.json();console.log(data);import osimport requests
res = requests.post( "https://restlink23telecom.com/api/v1/integrations/sms/send", headers={"X-API-Key": os.environ["API_KEY"], "Idempotency-Key": "crm-event-20260718-001"}, json={ "to": "+447700900123", "message": "Your booking is confirmed", "sender_id": "YourBrand" },)res.raise_for_status()print(res.json())<?php$ch = curl_init('https://restlink23telecom.com/api/v1/integrations/sms/send');curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('API_KEY'), 'Idempotency-Key: crm-event-20260718-001', 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode([ 'to' => '+447700900123', 'message' => 'Your booking is confirmed', 'sender_id' => 'YourBrand' ]),]);
$response = curl_exec($ch);$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);
if ($status !== 200) { throw new Exception("HTTP $status: $response");}$data = json_decode($response, true);print_r($data);require "net/http"require "json"
uri = URI("https://restlink23telecom.com/api/v1/integrations/sms/send")req = Net::HTTP::Post.new(uri)req["X-API-Key"] = ENV.fetch("API_KEY")req["Idempotency-Key"] = "crm-event-20260718-001"req["Content-Type"] = "application/json"req.body = { to: "+447700900123", message: "Your booking is confirmed", sender_id: "YourBrand"}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }raise "HTTP #{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)
puts JSON.parse(res.body)import java.net.URI;import java.net.http.HttpClient;import java.net.http.HttpRequest;import java.net.http.HttpResponse;
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://restlink23telecom.com/api/v1/integrations/sms/send")) .header("X-API-Key", System.getenv("API_KEY")) .header("Idempotency-Key", "crm-event-20260718-001") .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(""" { "to": "+447700900123", "message": "Your booking is confirmed", "sender_id": "YourBrand" }""")) .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.body());package main
import ( "fmt" "io" "net/http" "os" "strings")
func main() { payload := strings.NewReader(`{ "to": "+447700900123", "message": "Your booking is confirmed", "sender_id": "YourBrand"}`)
req, err := http.NewRequest("POST", "https://restlink23telecom.com/api/v1/integrations/sms/send", payload) if err != nil { panic(err) } req.Header.Set("X-API-Key", os.Getenv("API_KEY")) req.Header.Set("Idempotency-Key", "crm-event-20260718-001") req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close()
data, _ := io.ReadAll(res.Body) fmt.Println(string(data))}using System.Text;
using var client = new HttpClient();client.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("API_KEY"));client.DefaultRequestHeaders.Add("Idempotency-Key", "crm-event-20260718-001");
var json = """{ "to": "+447700900123", "message": "Your booking is confirmed", "sender_id": "YourBrand"}""";var content = new StringContent(json, Encoding.UTF8, "application/json");var response = await client.PostAsync("https://restlink23telecom.com/api/v1/integrations/sms/send", content);
response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync());| Value | Rule |
|---|---|
X-API-Key | Required restricted key with sms.send. JWT is not accepted |
| Idempotency | Send exactly one source: Idempotency-Key (generic clients, 1–128 characters using letters, numbers, _, -, ., :) or Customer.io’s automatic X-CIO-Idempotency-Key |
to | One E.164 phone number, for example +447700900123 |
message | Non-empty, maximum 4096 UTF-8 bytes and 10 SMS segments |
sender_id | 3–11 ASCII letters/numbers; cannot start with a number |
The full request body must be at most 32 KiB. Unknown fields, arrays and a
second JSON value are rejected. Do not send X-Workspace-ID: the API key is
already bound to the correct workspace.
Response
Section titled “Response”{ "status": "accepted", "message_id": "api_7_1784394012896569000_7c6d09d704f37e4b", "mode": "live"}Sandbox keys return the same shape with mode: "sandbox" and a test_...
message ID. Sandbox acceptance is a simulation and never creates a live SMS.
accepted means 23 Telecom durably recorded the live request for delivery. It
does not mean the handset received it. Use the returned message_id with
delivery webhooks or message status.
Safe retries
Section titled “Safe retries”Create one Idempotency-Key for one logical SMS. If the request times out or
returns 5xx, retry the exact same body with the same key.
Customer.io supplies X-CIO-Idempotency-Key automatically instead. Do not add
either idempotency header manually in Customer.io, and never send both sources.
They use separate internal namespaces, so one source cannot accidentally replay
the other.
- A completed retry returns the same
202body and message ID withIdempotent-Replayed: true. - The replay record is retained for 24 hours.
- Changing
to,messageorsender_idwhile reusing the key returns409 IDEMPOTENCY_KEY_REUSED. 409 IDEMPOTENCY_REQUEST_IN_PROGRESSincludesRetry-After; wait and retry.- The legacy
X-Idempotency-Keyis not accepted on this endpoint.
Production activation
Section titled “Production activation”- Complete the sandbox test with a dedicated
sk_test_...key. - Send your account manager the workspace and CRM/platform name.
- Your account manager enables integration sending for the workspace and confirms the restricted production key.
- Replace only the key with the confirmed
sk_prod_...key; the URL and body stay the same.
If live access is not enabled, the endpoint returns
403 INTEGRATION_NOT_ENABLED and creates no SMS.