Fast2SMS
Free API key · ₹50 test credit

SMS API for India:
Bulk SMS, OTP & WhatsApp

One key for DLT bulk SMS, OTP, Quick SMS, WhatsApp and Voice OTP. One HTTPS call to send, a webhook when it lands. Samples in cURL, PHP, Python, Node.js, Java, C#, Go and Ruby.

  • Free API key, no monthly fee
  • ₹50 test credit on signup
  • Delivery webhooks, free
  • 1–2 s typical OTP delivery
1 keySMS, OTP, WhatsApp, Voice
8Languages with samples
1–2 sTypical OTP delivery
₹100Minimum recharge
Get started

Your API key in 60 seconds

No sales call, no contract. The key is free and works for every channel on the same wallet.

  1. 1

    Sign up free

    Create your account with a mobile number. ₹50 test credit is added so the first calls cost nothing.

  2. 2

    Copy your API key

    Open Dev API in the panel and copy the key. You can also allow-list server IPs there.

  3. 3

    Pick your template ID

    DLT SMS: Bulk SMS → DLT Manager → Templates shows the Message ID. OTP: Smart OTP → Templates shows the OTP ID. WhatsApp: WhatsApp Manager → Templates shows the message_id. Quick SMS needs no ID.

  4. 4

    Send and listen

    Call /dev/bulkV2 or /dev/otp/send with that ID, then add a webhook URL to receive delivery updates in real time.

API explorer

Request, response and webhook, channel by channel

Pick a channel and a language. Every sample uses the real endpoint and parameter names from the API documentation; replace YOUR_API_KEY and the demo values.

Send a DLT SMS

GET https://www.fast2sms.com/dev/bulkV2
Request · cURL
curl "https://www.fast2sms.com/dev/bulkV2?route=dlt&sender_id=FSTSMS&message=111111&variables_values=Rahul%7C4821&numbers=9999999999%2C8888888888" \
  -H "Authorization: YOUR_API_KEY"
Request · PHP
<?php
$ch = curl_init();
$params = [
    'route' => 'dlt',
    'sender_id' => 'FSTSMS',
    'message' => '111111',
    'variables_values' => 'Rahul|4821',
    'numbers' => '9999999999,8888888888'
];
curl_setopt($ch, CURLOPT_URL, 'https://www.fast2sms.com/dev/bulkV2?' . http_build_query($params));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: YOUR_API_KEY']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result);
Request · Python
import requests

url = "https://www.fast2sms.com/dev/bulkV2"
headers = {"Authorization": "YOUR_API_KEY"}
params = {
    "route": "dlt",
    "sender_id": "FSTSMS",
    "message": "111111",
    "variables_values": "Rahul|4821",
    "numbers": "9999999999,8888888888"
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
Request · Node.js
// Node.js 18+ (built-in fetch)
const params = new URLSearchParams({
  route: "dlt",
  sender_id: "FSTSMS",
  message: "111111",
  variables_values: "Rahul|4821",
  numbers: "9999999999,8888888888"
});
const response = await fetch(`https://www.fast2sms.com/dev/bulkV2?${params}`, {
  headers: { Authorization: "YOUR_API_KEY" }
});
const result = await response.json();
console.log(result);
Request · Java
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://www.fast2sms.com/dev/bulkV2?route=dlt&sender_id=FSTSMS&message=111111&variables_values=Rahul%7C4821&numbers=9999999999%2C8888888888"))
    .header("Authorization", "YOUR_API_KEY")
    .GET()
    .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Request · C#
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "YOUR_API_KEY");
var response = await client.GetAsync("https://www.fast2sms.com/dev/bulkV2?route=dlt&sender_id=FSTSMS&message=111111&variables_values=Rahul%7C4821&numbers=9999999999%2C8888888888");
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
Request · Go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://www.fast2sms.com/dev/bulkV2?route=dlt&sender_id=FSTSMS&message=111111&variables_values=Rahul%7C4821&numbers=9999999999%2C8888888888", nil)
	req.Header.Set("Authorization", "YOUR_API_KEY")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
Request · Ruby
require "net/http"
require "json"

uri = URI("https://www.fast2sms.com/dev/bulkV2?route=dlt&sender_id=FSTSMS&message=111111&variables_values=Rahul%7C4821&numbers=9999999999%2C8888888888")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "YOUR_API_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
Where the Message ID comes from
  1. In the panel open Bulk SMS → DLT Manager → Templates.
  2. Copy the Message ID shown next to your approved template (or list them with GET /dev/dlt_manager).
  3. Send it as message, your approved header as sender_id, and the {#var#} values in order in variables_values, separated by |.

The same parameters work as a POST body.

Response200
{
  "return": true,
  "request_id": "lwdtp7cjyqxvfe9",
  "message": ["Message sent successfully"]
}
SMS delivery webhookPOST → you
{
  "request_id": "Sabcdef12345678",
  "route": "dlt",
  "sender_id": "FSTSMS",
  "mobile": "9999999999",
  "status": "delivered",
  "status_description": "Delivered successfully",
  "failure_reason": "",
  "sms_language": "english",
  "character_count": 120,
  "sms_count": 1,
  "amount_debited": "0.2000",
  "sent_timestamp": 1718712000,
  "sent_time": "2026-06-18 12:00:00",
  "delivery_timestamp": 1718712002,
  "delivery_time": "2026-06-18 12:00:02",
  "delivery_attempt": 1,
  "udf1": "order-4821",
  "udf2": "",
  "udf3": "",
  "post_attempt": 1,
  "webhook_type": "status_update"
}

Send an OTP

POST https://www.fast2sms.com/dev/otp/send
Request · cURL
curl -X POST "https://www.fast2sms.com/dev/otp/send" \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mobile": "9999999999",
    "otp_id": "YOUR_OTP_ID",
    "otp_length": 6,
    "otp_expiry": 15
}'
Request · PHP
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.fast2sms.com/dev/otp/send');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'mobile' => '9999999999',
    'otp_id' => 'YOUR_OTP_ID',
    'otp_length' => 6,
    'otp_expiry' => 15
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: YOUR_API_KEY', 'Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result);
Request · Python
import requests

url = "https://www.fast2sms.com/dev/otp/send"
headers = {"Authorization": "YOUR_API_KEY"}
payload = {
    "mobile": "9999999999",
    "otp_id": "YOUR_OTP_ID",
    "otp_length": 6,
    "otp_expiry": 15
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
Request · Node.js
// Node.js 18+ (built-in fetch)
const response = await fetch("https://www.fast2sms.com/dev/otp/send", {
  method: "POST",
  headers: { Authorization: "YOUR_API_KEY", "Content-Type": "application/json" },
  body: JSON.stringify({
  mobile: "9999999999",
  otp_id: "YOUR_OTP_ID",
  otp_length: 6,
  otp_expiry: 15
  })
});
const result = await response.json();
console.log(result);
Request · Java
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
String body = "{\"mobile\":\"9999999999\",\"otp_id\":\"YOUR_OTP_ID\",\"otp_length\":6,\"otp_expiry\":15}";
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://www.fast2sms.com/dev/otp/send"))
    .header("Authorization", "YOUR_API_KEY")
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Request · C#
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "YOUR_API_KEY");
var payload = new Dictionary<string, object> {
    ["mobile"] = "9999999999",
    ["otp_id"] = "YOUR_OTP_ID",
    ["otp_length"] = 6,
    ["otp_expiry"] = 15
};
var content = new StringContent(System.Text.Json.JsonSerializer.Serialize(payload),
    System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://www.fast2sms.com/dev/otp/send", content);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
Request · Go
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	body := `{"mobile":"9999999999","otp_id":"YOUR_OTP_ID","otp_length":6,"otp_expiry":15}`
	req, _ := http.NewRequest("POST", "https://www.fast2sms.com/dev/otp/send", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "YOUR_API_KEY")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
Request · Ruby
require "net/http"
require "json"

uri = URI("https://www.fast2sms.com/dev/otp/send")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req.body = {
  "mobile" => "9999999999",
  "otp_id" => "YOUR_OTP_ID",
  "otp_length" => 6,
  "otp_expiry" => 15
}.to_json
req["Authorization"] = "YOUR_API_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
Where the OTP ID comes from
  1. Open Smart OTP → Templates in the panel and create an OTP template (DLT or WhatsApp).
  2. Copy the OTP ID shown on the template card and send it as otp_id.
  3. Fast2SMS generates and stores the OTP; verify it with POST /dev/otp/verify {mobile, otp}.

Pass otp to use your own value, and {otp} inside variables_values when the template repeats it.

Response200
{
  "return": true,
  "status_code": 200,
  "request_id": "lwdtp7cjyqxvfe9",
  "message": "OTP sent successfully"
}
OTP delivery webhookPOST → you
{
  "request_id": "Oabcdef12345678",
  "route": "otp",
  "sender_id": "OTPSMS",
  "mobile": "9999999999",
  "status": "delivered",
  "status_description": "Delivered successfully",
  "channel": "sms",
  "type": "sms",
  "category": "AUTHENTICATION",
  "sms_count": 1,
  "amount_debited": "0.2000",
  "sent_time": "2026-06-18 12:00:00",
  "delivery_time": "2026-06-18 12:00:02",
  "failure_reason": "",
  "udf1": "login-7781",
  "post_attempt": 1,
  "webhook_type": "status_update"
}

Send a Quick SMS (no DLT)

GET https://www.fast2sms.com/dev/bulkV2
Request · cURL
curl "https://www.fast2sms.com/dev/bulkV2?route=q&message=Your%20order%204821%20is%20confirmed.%20Thank%20you%21&numbers=9999999999%2C8888888888" \
  -H "Authorization: YOUR_API_KEY"
Request · PHP
<?php
$ch = curl_init();
$params = [
    'route' => 'q',
    'message' => 'Your order 4821 is confirmed. Thank you!',
    'numbers' => '9999999999,8888888888'
];
curl_setopt($ch, CURLOPT_URL, 'https://www.fast2sms.com/dev/bulkV2?' . http_build_query($params));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: YOUR_API_KEY']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result);
Request · Python
import requests

url = "https://www.fast2sms.com/dev/bulkV2"
headers = {"Authorization": "YOUR_API_KEY"}
params = {
    "route": "q",
    "message": "Your order 4821 is confirmed. Thank you!",
    "numbers": "9999999999,8888888888"
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
Request · Node.js
// Node.js 18+ (built-in fetch)
const params = new URLSearchParams({
  route: "q",
  message: "Your order 4821 is confirmed. Thank you!",
  numbers: "9999999999,8888888888"
});
const response = await fetch(`https://www.fast2sms.com/dev/bulkV2?${params}`, {
  headers: { Authorization: "YOUR_API_KEY" }
});
const result = await response.json();
console.log(result);
Request · Java
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://www.fast2sms.com/dev/bulkV2?route=q&message=Your%20order%204821%20is%20confirmed.%20Thank%20you%21&numbers=9999999999%2C8888888888"))
    .header("Authorization", "YOUR_API_KEY")
    .GET()
    .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Request · C#
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "YOUR_API_KEY");
var response = await client.GetAsync("https://www.fast2sms.com/dev/bulkV2?route=q&message=Your%20order%204821%20is%20confirmed.%20Thank%20you%21&numbers=9999999999%2C8888888888");
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
Request · Go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://www.fast2sms.com/dev/bulkV2?route=q&message=Your%20order%204821%20is%20confirmed.%20Thank%20you%21&numbers=9999999999%2C8888888888", nil)
	req.Header.Set("Authorization", "YOUR_API_KEY")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
Request · Ruby
require "net/http"
require "json"

uri = URI("https://www.fast2sms.com/dev/bulkV2?route=q&message=Your%20order%204821%20is%20confirmed.%20Thank%20you%21&numbers=9999999999%2C8888888888")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "YOUR_API_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
No Message ID needed
  1. Write the text directly in message; no sender ID or template is required.
  2. Messages go out from a numeric sender after a short manual review.
  3. Add schedule_time (DD-MM-YYYY-HH-MM or a Unix timestamp) to send later.

For a branded sender and instant release, use route=dlt with a template Message ID.

Response200
{
  "return": true,
  "request_id": "lwdtp7cjyqxvfe9",
  "message": ["Message sent successfully"]
}
SMS delivery webhookPOST → you
{
  "request_id": "Sabcdef12345678",
  "route": "dlt",
  "sender_id": "FSTSMS",
  "mobile": "9999999999",
  "status": "delivered",
  "status_description": "Delivered successfully",
  "failure_reason": "",
  "sms_language": "english",
  "character_count": 120,
  "sms_count": 1,
  "amount_debited": "0.2000",
  "sent_timestamp": 1718712000,
  "sent_time": "2026-06-18 12:00:00",
  "delivery_timestamp": 1718712002,
  "delivery_time": "2026-06-18 12:00:02",
  "delivery_attempt": 1,
  "udf1": "order-4821",
  "udf2": "",
  "udf3": "",
  "post_attempt": 1,
  "webhook_type": "status_update"
}

Send a WhatsApp template message

GET https://www.fast2sms.com/dev/whatsapp
Request · cURL
curl "https://www.fast2sms.com/dev/whatsapp?message_id=1234&phone_number_id=109283746561&numbers=9999999999&variables_values=Rahul%7C4821" \
  -H "Authorization: YOUR_API_KEY"
Request · PHP
<?php
$ch = curl_init();
$params = [
    'message_id' => '1234',
    'phone_number_id' => '109283746561',
    'numbers' => '9999999999',
    'variables_values' => 'Rahul|4821'
];
curl_setopt($ch, CURLOPT_URL, 'https://www.fast2sms.com/dev/whatsapp?' . http_build_query($params));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: YOUR_API_KEY']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result);
Request · Python
import requests

url = "https://www.fast2sms.com/dev/whatsapp"
headers = {"Authorization": "YOUR_API_KEY"}
params = {
    "message_id": "1234",
    "phone_number_id": "109283746561",
    "numbers": "9999999999",
    "variables_values": "Rahul|4821"
}
response = requests.get(url, headers=headers, params=params)
print(response.json())
Request · Node.js
// Node.js 18+ (built-in fetch)
const params = new URLSearchParams({
  message_id: "1234",
  phone_number_id: "109283746561",
  numbers: "9999999999",
  variables_values: "Rahul|4821"
});
const response = await fetch(`https://www.fast2sms.com/dev/whatsapp?${params}`, {
  headers: { Authorization: "YOUR_API_KEY" }
});
const result = await response.json();
console.log(result);
Request · Java
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://www.fast2sms.com/dev/whatsapp?message_id=1234&phone_number_id=109283746561&numbers=9999999999&variables_values=Rahul%7C4821"))
    .header("Authorization", "YOUR_API_KEY")
    .GET()
    .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Request · C#
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "YOUR_API_KEY");
var response = await client.GetAsync("https://www.fast2sms.com/dev/whatsapp?message_id=1234&phone_number_id=109283746561&numbers=9999999999&variables_values=Rahul%7C4821");
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
Request · Go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://www.fast2sms.com/dev/whatsapp?message_id=1234&phone_number_id=109283746561&numbers=9999999999&variables_values=Rahul%7C4821", nil)
	req.Header.Set("Authorization", "YOUR_API_KEY")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
Request · Ruby
require "net/http"
require "json"

uri = URI("https://www.fast2sms.com/dev/whatsapp?message_id=1234&phone_number_id=109283746561&numbers=9999999999&variables_values=Rahul%7C4821")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "YOUR_API_KEY"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)
Where message_id and phone_number_id come from
  1. Open WhatsApp Manager → Templates in the panel; each approved template shows its message_id (also via GET /dev/dlt_manager/whatsapp?type=template).
  2. Open WhatsApp Manager → Numbers for the phone_number_id of your connected number (GET /dev/dlt_manager/whatsapp?type=number).
  3. Fill the template variables in order in variables_values; add media_url when the template has an image, video or PDF header.
Response200
{
  "status": true,
  "message": "Message sent successfully",
  "request_id": "6a3b2c1d"
}
WhatsApp status webhookPOST → you
{
  "request_id": "Wabcdef12345678",
  "route": "response",
  "mobile": "9999999999",
  "status": "delivered",
  "status_description": "Delivered successfully",
  "phone_number_id": "109283746561",
  "display_phone_number": "9999999999",
  "waba_id": "1283746561092",
  "recipient_id": "9999999999",
  "category": "UTILITY",
  "udf1": "order-4821",
  "post_attempt": 1,
  "webhook_type": "status_update"
}
Webhooks

Delivery reports pushed to you, in real time

A webhook is a URL on your server that Fast2SMS calls when a message changes state. Set it once; every delivered, failed, read or incoming event then arrives with the request_id you were given when you sent.

How it works

  1. Create the webhook in the panel (Webhooks → Create) or with the API below. Choose the service, the events, the method and the body format: JSON, form URL-encoded or XML.
  2. Send as usual. Pass udf1–udf3 with your own order or user IDs if you want them echoed back.
  3. Receive the event. Return HTTP 200 quickly and process afterwards. If your server is down, the delivery is retried and post_attempt counts the attempts.
  4. Verify, optionally. Turn on the signature in the panel and Fast2SMS adds a webhook_secret_key header with the secret you chose.
Create an SMS webhookPOST /dev/webhook/v2/sms
{
  "name": "Order updates",
  "url": "https://example.com/fast2sms/dlr",
  "event": "all",
  "method": "POST",
  "content_type": "application/json",
  "status": "active"
}

Same shape for /otp, /whatsapp and /rcs. GET lists, PUT /{id} edits and DELETE /{id} removes. Up to 10 webhooks per service.

SMS delivery webhookdelivered, failed
{
  "request_id": "Sabcdef12345678",
  "route": "dlt",
  "sender_id": "FSTSMS",
  "mobile": "9999999999",
  "status": "delivered",
  "status_description": "Delivered successfully",
  "failure_reason": "",
  "sms_language": "english",
  "character_count": 120,
  "sms_count": 1,
  "amount_debited": "0.2000",
  "sent_timestamp": 1718712000,
  "sent_time": "2026-06-18 12:00:00",
  "delivery_timestamp": 1718712002,
  "delivery_time": "2026-06-18 12:00:02",
  "delivery_attempt": 1,
  "udf1": "order-4821",
  "udf2": "",
  "udf3": "",
  "post_attempt": 1,
  "webhook_type": "status_update"
}

A failed message arrives with "status": "failed" and the operator reason in failure_reason (for example DND, handset switched off, number does not exist).

OTP delivery webhookdelivered, failed
{
  "request_id": "Oabcdef12345678",
  "route": "otp",
  "sender_id": "OTPSMS",
  "mobile": "9999999999",
  "status": "delivered",
  "status_description": "Delivered successfully",
  "channel": "sms",
  "type": "sms",
  "category": "AUTHENTICATION",
  "sms_count": 1,
  "amount_debited": "0.2000",
  "sent_time": "2026-06-18 12:00:00",
  "delivery_time": "2026-06-18 12:00:02",
  "failure_reason": "",
  "udf1": "login-7781",
  "post_attempt": 1,
  "webhook_type": "status_update"
}

channel tells you which leg delivered the OTP (sms, whatsapp or rcs) when Smart OTP fallback is on; phone_number_id and recipient_id are filled for the WhatsApp leg.

WhatsApp status webhooksent, delivered, read, failed, received
{
  "request_id": "Wabcdef12345678",
  "route": "response",
  "mobile": "9999999999",
  "status": "delivered",
  "status_description": "Delivered successfully",
  "phone_number_id": "109283746561",
  "display_phone_number": "9999999999",
  "waba_id": "1283746561092",
  "recipient_id": "9999999999",
  "category": "UTILITY",
  "udf1": "order-4821",
  "post_attempt": 1,
  "webhook_type": "status_update"
}

Incoming customer messages come to the same URL with "webhook_type": "incoming_message", plus from, message_type, body, and media_url, mime_type and caption for media. Button replies carry context_message_id.

RCS status webhooksent, delivered, read, failed, received
{
  "request_id": "Rabcdef12345678",
  "route": "response",
  "mobile": "9999999999",
  "status": "delivered",
  "status_description": "Delivered successfully",
  "bot_id": "your_rcs_bot",
  "error_message": "",
  "ts": "1718712002",
  "udf1": "order-4821",
  "post_attempt": 1,
  "webhook_type": "status_update"
}

A non-RCS handset reports as failed so your code can fall back to SMS. Incoming replies use "webhook_type": "incoming_message" with from, message_type and body.

Error codes

Every error tells you what to fix

Success is HTTP 200 with "return": true. Errors come back as HTTP 400 or 401 with "return": false, a status_code and a plain-English message.

HTTPstatus_codeMessageWhat to do
400401Sender ID MissingAdd sender_id (DLT routes).
400402Message Text MissingAdd message: template ID for route=dlt, text for route=q.
400403Route MissingAdd route (dlt, q, dlt_manual or otp).
400405Numbers MissingAdd numbers, comma separated.
400406Invalid Sender IDUse a sender ID approved in DLT Manager.
400407Invalid words used in messageRemove blocked words from the text.
400408Invalid RouteCheck the route value.
400411Invalid NumbersUse 10-digit Indian mobile numbers.
401412Invalid Authentication, Check Authorization KeyCopy the key again from Dev API in the panel.
401413Invalid Authentication, Authorization Key DisabledEnable the key, or generate a new one.
400414IP is blacklisted from Dev API sectionAllow your server IP under Dev API.
400415Account DisabledContact support.
400416You don't have sufficient wallet balanceRecharge, then retry.
400417Use english letters or change language to unicodePass language=unicode for Indian languages.
400422Invalid Variable Values or Some Values MissingSend one value per {#var#}, separated by |.
400424Same content to same number within 10 seconds not allowedDeduplicate on your side; retry after 10 s.
400425Invalid Message IDUse the Message ID shown in DLT Manager.
400426Invalid link used in variablesLinks in variables must match the approved template.
400427Number blocked in Fast2SMS DND listThe recipient opted out at fast2sms.com/dnd.
400990Template/Sender id blacklisted at DLTRe-register the template or header on the DLT portal.
400996Spamming detectedDo not send the same SMS repeatedly to one number.
400997Before using OTP SMS API, complete KYCFinish KYC in the panel.
400998Only numeric variable_values is allowed in OTP routeSend digits only, up to 10.
400999Use DLT or Quick SMS route for sending Bulk SMSSwitch to route=dlt or route=q.

The full list, including DLT Manager and OTP-specific codes, is in the docs.

Best practices

Small habits that keep an integration boring and reliable

Batch your numbers

Send a few thousand numbers per request instead of one request per number. Each request gets one request_id, which makes retries and reports easy.

Keep the request_id

Store it against your order or user. Webhooks and GET /dev/dlr/{request_id} both use it, so you never need to guess which message a report belongs to.

Schedule instead of sleeping

Use schedule_time (DD-MM-YYYY-HH-MM or a Unix timestamp) and let Fast2SMS hold the campaign. No cron job needed on your side.

Set language for Indian scripts

Pass language=unicode for Hindi, Marathi, Tamil and other scripts. A Unicode part is 70 characters; sms_count in the webhook shows the parts used.

Fill variables, not templates

With route=dlt you send the template ID and variables_values. The text itself never leaves the approved template, so DLT mismatches cannot happen.

Use udf fields for tracking

udf1, udf2 and udf3 travel with the message and come back in every webhook. Put your order ID, campaign name or tenant there.

Retry only what is safe

Retry timeouts and 5xx with back-off. Never retry after a 200, and fix the cause before retrying a 4xx; the message field tells you what it is.

Keep the key on the server

Call the API from your backend, allow-list your server IPs under Dev API, and rotate the key if it is ever committed to a repository.

No code needed?

Plugins and integrations, all free

If your stack is Shopify, WooCommerce, WordPress, Tally, HubSpot, Zoho, Pipedrive, Freshworks, Google Sheets or Pabbly Connect, there is a ready integration that uses this same API under the hood. Nothing to host, no plugin charge; you pay the per-message rate only.

  • REST, GET or POSTQuery string or JSON body, your choice.
  • Webhooks in JSON, form or XMLAny method, any body shape you edit.
  • Analytics and logs API/dev/sms_summary and /dev/sms_logs by date.
  • Help from peopleChat, phone and email; DLT questions included.
Compare

What a developer gets here versus elsewhere

What you get Fast2SMS Many providers
API key Free, from the panel in a minute Paid plan or sales call first
Monthly or setup fee None, pay per message Platform rental is common
Minimum recharge ₹100 ₹1,000–₹5,000 packs
Delivery webhooks Free, JSON / form / XML, per event Often a paid add-on
Delivery speed 1–2 s typical for OTP Varies by route
Same rate in panel and API Yes API often priced separately
DLT help Free, templates checked Paid or self-service
Code samples 8 languages on this page and in the docs cURL only
OTP generate + verify Built in (send, verify, resend) You store OTPs yourself
WhatsApp and Voice on one key Yes Separate accounts

Based on publicly listed plans of SMS API providers in India; check each provider for current terms.

Pricing

The API costs exactly what the panel costs

There is no API tier. Recharge from ₹100 and every message, whether sent from the panel, a plugin or your code, is charged at the same per-message rate. SMS starts at ₹0.25 and falls to ₹0.11 with volume; WhatsApp and RCS have their own per-message rates. New accounts get ₹50 to test with.

  • ₹0.11–₹0.25 per SMSRate set by your recharge amount.
  • No setup, no monthly feePrepaid wallet, GST invoice per recharge.
  • ₹100 minimum rechargeBalance never expires.
  • ₹50 free on signupEnough to test every endpoint.
Data security

Your data is encrypted and stays yours

Your customers' numbers and messages are business data. We treat them that way: encrypted on the wire and in the database, visible only to the people you allow, on infrastructure that is audited every year.

See our certificates
  • Encrypted in transitEvery panel page and API call runs over HTTPS (TLS 1.2+). Your API key is sent in a header, never in a page.
  • Encrypted at restSensitive fields such as API keys, contact numbers, message content and delivery reports are encrypted in our database, with keys kept outside the application.
  • Access you controlPer-user API keys you can rotate any time, IP allow-listing, OTP on login, and agents who see only what you assign.
  • Audited and certifiedISO 27001 and SOC 2 Type 2 certified, VAPT tested by an external team, MSME registered.
ISO 27001 certified SOC 2 Type 2 VAPT certified MSME registered
Support

Stuck on an integration? Ask a person

Chat from inside the panel, email [email protected] or call +91 6262778822. Send the request_id or the error message and the team can see exactly what happened. For a guided setup or volume pricing, book a meeting.

  • Live chat in the panelFastest for API questions.
  • +91 6262778822Support line, answered by people.
  • [email protected]Replies within the working day.
FAQ

SMS API questions, answered

77 questions developers ask before and after integrating, grouped by topic.

Getting started

How do I get a Fast2SMS API key?

Sign up free at fast2sms.com, open the Dev API page in the panel and copy the key shown there. The key is free; you only pay per message you send. You can regenerate it any time if it leaks.

Is the SMS API free to use?

The key and every endpoint are free. You pay the normal per-message rate for messages that go out, the same rate you pay in the panel. New accounts get ₹50 of test credit.

Which URL do I call to send an SMS?

GET or POST https://www.fast2sms.com/dev/bulkV2 with your key in the Authorization header. Use route=dlt with your sender ID and template Message ID, or route=q for Quick SMS without DLT.

Can I test the API before buying?

Yes. The ₹50 signup credit covers test messages. The response tells you immediately whether the request was accepted, and a webhook or GET /dev/dlr/{request_id} tells you whether it was delivered.

Do I need DLT registration to use the API?

For route=dlt, yes: a DLT-registered entity, sender ID and template are required under TRAI rules, and our team helps you register for free. For Quick SMS (route=q) no DLT registration is needed.

Is there an API for the wallet balance?

Yes. POST https://www.fast2sms.com/dev/wallet with the Authorization header returns your balance and how many SMS it covers at your current rate.

Where is the full API documentation?

docs.fast2sms.com has every endpoint with an interactive try-it panel, including SMS, OTP, WhatsApp, DLT Manager, analytics and webhooks. This page shows the most used calls in eight languages.

Is there a sandbox or test mode?

There is no separate sandbox. Use the free credit on real numbers, which gives you a true delivery result rather than a simulated one.

Authentication

How do I authenticate API requests?

Send the header Authorization: YOUR_API_KEY on every request. No OAuth, no token exchange.

What happens if the key is wrong?

The API answers HTTP 401 with status_code 412 (invalid key) or 413 (key disabled). Copy the key again from Dev API or enable it.

Can I restrict the key to my server IP?

Yes. The Dev API page lets you allow-list IPs. Requests from other addresses get status_code 414.

Can I rotate or regenerate the key?

Yes, from the same Dev API page. Update your server config right after, since the old key stops working at once.

Should I call the API from a browser or mobile app?

No. The key would be visible to anyone. Call the API from your server and expose your own endpoint to the app.

DLT and bulk SMS

What is the difference between route=dlt and route=dlt_manual?

route=dlt uses a template saved in DLT Manager, so you pass the Message ID and the variable values. route=dlt_manual lets you pass the full approved text plus template_id and entity_id yourself.

How do I pass template variables?

Use variables_values with the values in the order the {#var#} slots appear, separated by a pipe, for example Rahul|4821|1299. Values must match what the template allows.

How many numbers can I send to in one request?

Pass them comma separated in numbers. Large lists are accepted; for lakhs of numbers split into batches of a few thousand so each request stays small and easy to retry.

Can I schedule an SMS through the API?

Yes. Add schedule_time in DD-MM-YYYY-HH-MM format or as a Unix timestamp. Scheduled sends can be cancelled from the panel before they start.

How do I send Hindi or other Indian languages?

Pass language=unicode (required for dlt_manual). A Unicode part holds 70 characters instead of 160, which the sms_count field reflects.

Does the same key work for promotional and transactional SMS?

Yes. The route and the DLT template type decide how the message is treated; the key is the same.

How do I get the SMS cost in the response?

Add sms_details=1 to the request. The response then includes character count, per-SMS rate and amount debited.

Can I track my own order or user ID per message?

Yes. Send udf1, udf2 and udf3 with any text. They are echoed back in the delivery webhook so you can match it without a lookup.

Can I manage DLT sender IDs and templates through the API?

Yes. GET /dev/dlt_manager?type=sender or type=template lists them, and POST /dev/dlt_manager/add_sender, add_entity and add_template submit new ones.

Is there an analytics API?

GET /dev/sms_summary?from=YYYY-MM-DD&to=YYYY-MM-DD returns sent, delivered, pending, failed, blocked and error counts, and GET /dev/sms_logs returns the per-message log for a date range.

OTP API

How does the OTP API work?

POST /dev/otp/send with mobile and your OTP template ID. Fast2SMS generates the code, sends it and stores it; POST /dev/otp/verify checks what the user typed.

Can I use my own OTP value?

Yes. Pass otp in the send request and Fast2SMS sends that value instead of generating one.

How long is an OTP valid?

otp_expiry sets it in minutes, from 1 to 10080. The default is 15 minutes.

What OTP lengths are supported?

otp_length from 4 to 10 digits; the default is 6.

Can the user ask for the OTP again?

POST /dev/otp/resend with the mobile number resends the last OTP. The limit is 5 resends, after which the API returns a clear message.

What is Smart OTP fallback?

When an OTP SMS is not delivered, the same OTP can go out on WhatsApp (and back). The webhook channel field tells you which leg delivered it.

Does the OTP route need KYC?

Yes. The API returns status_code 997 until KYC is complete in the panel, which takes a few minutes.

Quick SMS

What is Quick SMS in the API?

route=q sends free-text SMS without a DLT sender ID or template. It goes through a short manual review before release and is meant for occasional, non-DLT sending.

Why did my Quick SMS not go out immediately?

Quick SMS campaigns are reviewed before release. The API accepts the request at once and the message is sent after review.

Can I send Quick SMS to DND numbers?

Quick SMS uses an international gateway and reaches all Indian numbers including DND, but it must not be used for spam; repeated rejections block the route.

WhatsApp API

How do I send a WhatsApp message through the API?

Call GET /dev/whatsapp with message_id (the Fast2SMS ID of an approved template), phone_number_id and numbers. Add variables_values for template variables and media_url for a media header.

Where do I find message_id and phone_number_id?

In WhatsApp Manager inside the panel, or call GET /dev/dlt_manager/whatsapp?type=template or type=number.

Can I send a free-form WhatsApp message?

Inside the 24-hour customer service window, yes: POST /dev/whatsapp-session with a text, image, document, audio, video, location, sticker or reaction body.

Do I get delivered and read receipts?

Yes. Create a WhatsApp webhook and you receive sent, delivered, read and failed events, plus incoming messages, each with the request_id you sent.

Is there a Meta-format API?

Yes. The advanced endpoints accept the same JSON that Meta Cloud API uses, so existing integrations port over by changing the URL and key. Details are in the docs.

What does the WhatsApp API cost?

No setup or monthly fee. You pay per message by category (utility, authentication, marketing, service); see the WhatsApp pricing page.

Voice OTP

Can I send an OTP by Voice call?

Yes, through the same Smart OTP API. Create an OTP template in the panel (Smart OTP → Templates) with Voice call as its channel, then call POST /dev/otp/send with the mobile number and the otp_id of that template; the code is read out on a call. You can set SMS or WhatsApp as the fallback on the template, or use Voice as the fallback of an SMS template, with no change to your code.

How is a Voice OTP charged?

Per 15-second pulse, only when the call is answered. Missed or unanswered calls are not charged.

Can I send bulk Voice campaigns through the API?

Bulk Voice campaigns (OBD) are run from the panel with an MP3 and a number list. Voice OTP is the API route; see the Voice Call page for OBD.

Webhooks and delivery reports

How do I receive delivery reports automatically?

Create a webhook in the panel (Webhooks → Create) or with POST /dev/webhook/v2/sms. Fast2SMS then sends a request to your URL for each event with the status and reason.

Which events can a webhook subscribe to?

SMS and OTP: delivered and failed. WhatsApp and RCS: sent, delivered, read, failed and incoming messages. You can also choose all events.

What format is the webhook payload?

Your choice: JSON, form URL-encoded or XML, sent by POST, GET or PUT. The standard body is shown above and you can edit it in the panel.

How should my server respond to a webhook?

Return HTTP 200 quickly and process the data afterwards. If your URL fails, Fast2SMS retries; post_attempt in the payload tells you which attempt it is.

Can I have more than one webhook?

Yes, up to 10 per service, so staging and production, or different tools, can each have their own URL.

How do I verify that a webhook really came from Fast2SMS?

Enable the webhook signature in the panel. Fast2SMS then sends a secret key header you set, which your server compares before trusting the payload.

Can I fetch a report instead of using a webhook?

Yes. GET /dev/dlr/{request_id} returns the status of every number in that request for the last 7 days.

Errors and limits

What does "return": false mean?

The request was not accepted. Read status_code and message: 412 and 413 are key problems, 416 is balance, 406, 425 and 426 are DLT template or sender issues.

Why do I get "Same content to same number within 10 seconds not allowed"?

The API blocks identical messages to the same number within 10 seconds to stop accidental loops. Deduplicate retries on your side.

Why does the API say my IP is blacklisted?

You enabled IP allow-listing under Dev API and the request came from another address. Add the new server IP or clear the list.

Is there a rate limit?

Normal application traffic is not throttled. Batches of a few thousand numbers per request and a short pause between batches keep large campaigns smooth; contact support for very high throughput.

What should I retry and what should I not?

Retry network errors and HTTP 5xx with a short back-off. Do not retry 4xx responses without fixing the cause, and never resend after a 200 with return true.

Why do I get 999 "Use DLT or Quick SMS route"?

An old route value was used. Switch to route=dlt or route=q as documented.

Languages and frameworks

How do I send SMS from PHP?

Use cURL or Guzzle to call /dev/bulkV2 with the Authorization header, as in the PHP sample above. Works with plain PHP, Laravel, CodeIgniter and WordPress plugins alike.

How do I send SMS from Laravel?

Use the Http facade: Http::withHeaders(['Authorization' => $key])->get('https://www.fast2sms.com/dev/bulkV2', $params). Keep the key in .env and send OTPs from a queued job.

How do I send SMS from WordPress or WooCommerce?

Install the Fast2SMS WordPress plugin or call the API with wp_remote_get() from a hook such as woocommerce_order_status_completed. See the Integrations page.

How do I send SMS from Python?

Use requests.get() or requests.post() with a headers dict, as in the Python sample. The same code runs in scripts, cron jobs and web apps.

How do I send SMS from Django or Flask?

Call the API from a view or a Celery task with requests. Store the key in settings or an environment variable, and send OTPs from a background task so the response stays fast.

How do I send SMS from Node.js or Express?

Node 18+ has fetch built in; the Node sample above works as is. In Express, call it inside your route handler and await the JSON.

How do I send SMS from Java or Spring Boot?

Use java.net.http.HttpClient as in the Java sample, or Spring's RestTemplate / WebClient with the Authorization header.

How do I send SMS from C# or .NET?

Use HttpClient with DefaultRequestHeaders.Add("Authorization", key), as in the C# sample. Works in ASP.NET Core, console apps and Azure Functions.

How do I send SMS from Go?

Use net/http with a request that sets the Authorization header, as in the Go sample. No extra library is needed.

How do I send SMS from Ruby on Rails?

Use Net::HTTP as in the Ruby sample, or the HTTParty gem. Send from an ActiveJob so the request does not block the web process.

Can I send SMS from an Android or Flutter app?

Call your own backend, and let the backend call Fast2SMS. Putting the API key in a mobile app exposes it to anyone who decompiles the app.

Can I send SMS from Google Sheets or Excel?

Yes. The Google Sheets integration sends from a sheet, and Apps Script can call /dev/bulkV2 with UrlFetchApp. Excel users can send through the panel upload.

Does Fast2SMS work with Zapier, Pabbly or Make?

Yes. Pabbly Connect is a ready integration, and any automation tool with a Webhook or HTTP step can call /dev/bulkV2 directly.

Are there official SDKs?

The API is a plain HTTPS call, so no SDK is required. The samples on this page and the docs cover cURL, PHP, Python, Node.js, Java, C#, Go and Ruby.

Pricing and security

How much does an SMS cost through the API?

The same as in the panel: from ₹0.25 per SMS at ₹100 recharge down to ₹0.11 at higher volumes. WhatsApp and RCS have their own per-message rates.

Is there a monthly fee for API access?

No. There is no setup fee, no monthly rental and no API surcharge. You recharge from ₹100 and pay per message.

Is the API served over HTTPS?

Yes. All endpoints are HTTPS only, and your API key never travels in the URL path; it stays in the header.

Where is customer data stored?

Encrypted in transit and at rest. Fast2SMS is ISO 27001 and SOC 2 Type 2 certified and VAPT tested; see the Certificates page.

How do I stop a number from receiving my messages?

Keep your own opt-out list, and respect the Fast2SMS DND list: a number blocked there returns status_code 427 and is not charged.

Can I get an invoice for API usage?

Yes. Every recharge produces a GST invoice in the panel, and the transaction log shows each debit.

Who do I contact for API help?

Chat inside the panel, email [email protected] or call +91 6262778822. For volume pricing or a technical walkthrough, schedule a meeting with the team.

Can I use one key for SMS, WhatsApp and Voice?

Yes. One account, one wallet and one key cover every channel, and the webhooks for each channel can point at the same URL.

Get your free API key and send the first SMS today

₹50 test credit after signup. Pay as you go from ₹100, no monthly fee, webhooks included.