Send WhatsApp from any app
SmsPort exposes a Twilio-style REST API so you can send WhatsApp messages, check delivery status, and receive real-time webhooks — all using a single API key. No username or password sharing required.
Generate your API Key
From your SmsPort dashboard, go to Setup → API Keys & Developer Settings. Click New API Key, give it a label (e.g. "My CRM"), choose live environment, and optionally enter a webhook URL for delivery receipts. Click Generate Key.
Your key will look like:
sk_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6Base URL & Authentication
All public API requests go to the base URL below. Authenticate by passing your key in theAuthorization header.
Base URL: https://api.smsport.inAuthorization: ApiKey sk_live_your_key_here
# Also accepted:
X-Api-Key: sk_live_your_key_hereFind your Sender Phone Number ID
Every message needs a from field — this is your WhatsApp Phone Number ID (not the phone number itself). List available senders:
curl https://api.smsport.in/api/v1/senders \
-H "Authorization: ApiKey sk_live_your_key_here"{
"senders": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+917600000000",
"verifiedName": "MyBusiness",
"status": "connected"
}
]
}id field — this is your Phone Number ID used as from in all message requests.Send a Message
4a — Send a text message
curl -X POST https://api.smsport.in/api/v1/messages/send/text \
-H "Authorization: ApiKey sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"from": "550e8400-e29b-41d4-a716-446655440000",
"to": "+919876543210",
"text": "Hello! Your order #1234 has been shipped. 🚀"
}'{
"message": {
"id": "msg-uuid-here",
"sid": "msg-uuid-here",
"status": "queued",
"to": "+919876543210",
"from": "550e8400-e29b-41d4-a716-446655440000",
"kind": "text",
"metaMessageId": null,
"createdAt": "2026-05-23T18:30:00Z"
}
}4b — Send a template message
Templates must be pre-approved by Meta. Use GET /api/v1/templates to list templates, GET /api/v1/templates/:id for ID-based definition lookup, or GET /api/v1/templates/resolve?name=...&language=... for exact name+language lookup.
curl -X POST https://api.smsport.in/api/v1/messages/send/template \
-H "Authorization: ApiKey sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"from": "550e8400-e29b-41d4-a716-446655440000",
"to": "+919876543210",
"templateName": "order_confirmation",
"languageCode": "en",
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Rahul" },
{ "type": "text", "text": "#ORD-5678" }
]
}
]
}'4c — Send an image / media message
curl -X POST https://api.smsport.in/api/v1/messages/send/media \
-H "Authorization: ApiKey sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"from": "550e8400-e29b-41d4-a716-446655440000",
"to": "+919876543210",
"mediaType": "image",
"mediaUrl": "https://yourcdn.com/invoice.jpg",
"caption": "Your invoice for March 2026"
}'Template definitions for safe runtime mapping
Pull the template definition before send so your runtime values align with Meta placeholder order.
# List templates (APPROVED by default)
curl "https://api.smsport.in/api/v1/templates?language=en_US&limit=50&offset=0" \
-H "Authorization: ApiKey sk_live_your_key_here"
# Get one template with components and placeholders
curl "https://api.smsport.in/api/v1/templates/30c095f8-48db-4125-95ef-628dd63b17c5" \
-H "Authorization: ApiKey sk_live_your_key_here"
# Resolve by exact name + language
curl "https://api.smsport.in/api/v1/templates/resolve?name=appointment_notification&language=en_US" \
-H "Authorization: ApiKey sk_live_your_key_here"{
"template": {
"id": "30c095f8-48db-4125-95ef-628dd63b17c5",
"name": "appointment_notification",
"language": "en_US",
"category": "UTILITY",
"status": "APPROVED",
"components": [
{
"type": "BODY",
"text": "Hello {{1}}, your appointment with {{2}} is confirmed.",
"requiredVariables": 2,
"variableOrder": ["{{1}}", "{{2}}"]
}
],
"updatedAt": "2026-05-24T10:30:00.000Z"
}
}Check Message Status
Use the id returned at send time to poll delivery status:
curl https://api.smsport.in/api/v1/messages/msg-uuid-here \
-H "Authorization: ApiKey sk_live_your_key_here"Message status progresses through:
Receive Delivery Webhooks
Set WhatsApp Webhook (Panel)
You can set a webhook URL to receive real-time WhatsApp delivery reports and inbound messages as a JSON POST object.
Set the webhook here:
Visit your API Integration Dashboard → Scroll to Webhook Configuration → Toggle Enable Webhooks, paste your URL, and click "Save Configuration".
Webhook payloads
SmsPort pushes two types of events to your webhook: Delivery Receipts and Inbound Replies.
// Outbound Delivery Receipt
{
"event": "message.delivered",
"apiVersion": "v1",
"messageId": "msg-uuid-here",
"tenantId": "tenant-uuid-here",
"status": "delivered",
"to": "+919876543210",
"from": "550e8400-e29b-41d4-a716-446655440000",
"metaMessageId": "wamid.HBgM...",
"errorMessage": null,
"timestamp": "2026-05-23T18:35:00Z"
}
// Inbound Customer Reply
{
"event": "message.received",
"apiVersion": "v1",
"messageId": "msg-inbound-uuid",
"tenantId": "tenant-uuid-here",
"status": "received",
"to": "550e8400-e29b-41d4-a716-446655440000",
"from": "+919876543210",
"text": "Thank you for the receipt!",
"timestamp": "2026-05-23T18:36:00Z"
}Webhook Response Fields
| Field | Type | Description |
|---|---|---|
| event | String | The type of event: message.delivered or message.received |
| messageId | String | Unique SmsPort identifier for the message. |
| status | String | The delivery status (e.g., sent, delivered, read, failed, received) |
| to | String | For outbound, the recipient's phone number. For inbound, your Sender ID. |
| from | String | For outbound, your Sender ID. For inbound, the customer's phone number. |
| metaMessageId | String | The underlying WhatsApp (wamid) identifier. |
| text | String | The content of the customer's message (only present for message.received). |
| errorMessage | String | null | Contains error details if the message failed to deliver. |
| timestamp | String (ISO 8601) | The UTC date and time when the event occurred. |
Verify the webhook signature
Every request includes X-SmsPort-Signature: sha256=<hmac>. Verify it using your webhook secret (shown when you create the key):
const crypto = require('crypto');
function verifyWebhook(req, webhookSecret) {
const signature = req.headers['x-smsport-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', webhookSecret)
.update(JSON.stringify(req.body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express example
app.post('/webhook/smsport', express.json(), (req, res) => {
if (!verifyWebhook(req, process.env.SMSPORT_WEBHOOK_SECRET)) {
return res.status(401).send('Unauthorized');
}
const { event, messageId, status, to } = req.body;
console.log(`Message ${messageId} to ${to} is now: ${status}`);
res.sendStatus(200); // Always respond 200 quickly
});Complete Code Examples
Copy and paste these full working examples into your application.
const SMSPORT_API_KEY = process.env.SMSPORT_API_KEY; // sk_live_...
const PHONE_NUMBER_ID = process.env.SMSPORT_PHONE_NUMBER_ID;
const BASE_URL = 'https://api.smsport.in';
async function sendWhatsApp(to, text) {
const res = await fetch(`${BASE_URL}/api/v1/messages/send/text`, {
method: 'POST',
headers: {
'Authorization': `ApiKey ${SMSPORT_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ from: PHONE_NUMBER_ID, to, text }),
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.message || 'Send failed');
}
return res.json(); // { message: { id, status, ... } }
}
// Usage
const { message } = await sendWhatsApp('+919876543210', 'Hello from my app!');
console.log('Queued:', message.id);All Public Endpoints
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/senders | List phone numbers | ApiKey |
| GET | /api/v1/templates | List templates (filters: status,name,language,limit,offset) | ApiKey |
| GET | /api/v1/templates/:id | Get full template definition (components + placeholder order) | ApiKey |
| GET | /api/v1/templates/resolve?name=...&language=... | Resolve full template by exact name+language | ApiKey |
| POST | /api/v1/messages/send/text | Send text message | ApiKey |
| POST | /api/v1/messages/send/media | Send image/doc/audio/video | ApiKey |
| POST | /api/v1/messages/send/template | Send template message | ApiKey |
| GET | /api/v1/messages/:id | Get message status | ApiKey |
| GET | /api/v1/messages | List recent messages | ApiKey |
| POST | /api/v1/keys | Create API key | JWT (dashboard) |
| GET | /api/v1/keys | List API keys | JWT (dashboard) |
| PATCH | /api/v1/keys/:id | Update label/webhook URL | JWT (dashboard) |
| DELETE | /api/v1/keys/:id | Revoke API key | JWT (dashboard) |
Error Handling
| HTTP Status | Meaning | Fix |
|---|---|---|
| 401 | Invalid or missing API key | Check Authorization header format |
| 400 | Validation error | Check request body — see message field |
| 404 | Message not found | Use correct message ID for your tenant |
| 429 | Rate limit exceeded | Back off and retry after a few seconds |
| 500 | Server error | Retry with exponential backoff |
// All errors return this shape:
{
"statusCode": 401,
"message": "Invalid or revoked API key.",
"error": "Unauthorized"
}Idempotency — Prevent Duplicate Sends
Pass a unique idempotencyKey in your request body. If the same key is sent twice, SmsPort returns the original message without sending a duplicate — safe for retries.
{
"from": "phone-number-id",
"to": "+919876543210",
"text": "Your OTP is 4821",
"idempotencyKey": "otp-user-42-attempt-1"
}Next Steps
- → Generate your API key from the dashboard
- → Full API Reference — every endpoint with all fields
- → Senders Flow Guide — connect your WhatsApp number
- → Templates — create and approve message templates