How to Send & Receive SMS with Cliqtel
Overview
Cliqtel SMS lets you send and receive text messages on any SMS-enabled phone number in your account. Messages can be managed through two channels:
- Portal — send and receive messages directly in the Cliqtel dashboard under the SMS tab. No code required.
- REST API — integrate SMS into your application for automated messaging, notifications, and customer engagement.
Inbound SMS is free. Outbound messages are charged per segment from your wallet balance.
How it works
- Purchase a number with SMS capability (or enable SMS on an existing number)
- Send outbound SMS from the portal or API
- Receive inbound SMS in your portal inbox or via webhook
- Track delivery status in real-time
Prerequisites
- A Cliqtel account with wallet balance (sign up or top up)
- An active phone number in a country that supports SMS (see SMS-capable countries)
- For API usage: your API key from the API Keys tab in the portal
Step 1 — Enable SMS on a Number
Option A: During checkout
When ordering new numbers, SMS-capable numbers show an "SMS" checkbox in Step 2 of the order flow. Check the box to enable SMS — the add-on cost is shown next to each number.
Option B: On an existing number
- Go to My Numbers in the portal
- Click Configure on the number you want to SMS-enable
- Find the SMS capability toggle in the settings panel
- Toggle it on — you'll see the monthly price and a confirmation prompt
- Confirm — the SMS add-on fee is charged from your wallet
| Setting | Value |
|---|---|
| Inbound SMS | Free to receive |
| Message segment | 160 characters (GSM-7) or 70 characters (Unicode) |
| Billing | Charged from wallet balance (postpaid available on request) |
Step 2 — Send SMS from the Portal
+31612345678)
The message appears in your SMS inbox immediately with a delivery status indicator. Status updates from queued → sent → delivered in real-time.
Step 3 — View Your SMS Inbox
All inbound and outbound messages appear in the SMS tab of your portal. You can:
- Filter by number — select a specific DID to see only its messages
- Filter by direction — show only inbound or outbound
- View stats — messages today, this month, and 30-day delivery rate
Step 4 — Send SMS via API
Send SMS programmatically using the Cliqtel REST API. The base URL is
https://cliqtel.com/api/v1/sms and every request authenticates with your API key in the
Authorization: Bearer YOUR_API_KEY header. Create API keys on the
API Keys tab in the portal.
{
"from": "+31201234567",
"to": "+31612345678",
"body": "Hello from Cliqtel!"
}curl -X POST https://cliqtel.com/api/v1/sms/messages \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "+31201234567",
"to": "+31612345678",
"body": "Your order #1234 has shipped!"
}'{
"data": {
"id": 42,
"from": "+31201234567",
"to": "+31612345678",
"body": "Your order #1234 has shipped!",
"direction": "outbound",
"status": "queued",
"segments": 1,
"cost": 0.008,
"currency": "EUR",
"carrier": "cliqtel",
"created_at": "2026-04-03T10:15:30+00:00"
}
}| Parameter | Type | Required | Description |
|---|---|---|---|
from | string | Yes* | Your SMS-enabled number in E.164 format (e.g. +31201234567) |
from_number_id | integer | Yes* | Alternatively, the numeric ID of your number (from GET /api/v1/sms/numbers) |
to | string | Yes | Destination number in E.164 format (e.g. +31612345678) |
body | string | Yes | Message text, max 1,600 characters |
status_callback_url | string | No | URL to receive delivery-status webhooks |
* Provide either from or from_number_id, not both.
Step 5 — Receive SMS via Webhook
To receive inbound SMS in your application, configure a webhook URL. Cliqtel will send an HTTP POST to your endpoint for each incoming message.
{
"event": "sms.received",
"data": {
"id": 157,
"from": "+31612345678",
"to": "+31201234567",
"body": "Thanks, got the tracking!",
"segments": 1,
"received_at": "2026-04-03T10:16:45.000000Z"
}
}Enable per-number webhook forwarding from your dashboard: open the number under
My Numbers → SMS, set Forward inbound → Webhook
and enter your HTTPS endpoint. Every inbound message to that number is then delivered to your URL as the
JSON payload shown above. Reply to a conversation by sending an SMS back from the same number
(from) — that is what makes the thread two-way.
GET https://cliqtel.com/api/v1/sms/messages
at any time (see List messages), or read every thread in the portal inbox.
Webhook delivery and polling work together.
Step 6 — Track Delivery Status
Every outbound message goes through these status transitions:
| Status | Meaning |
|---|---|
queued | Message accepted and queued for delivery |
sending | Being transmitted to the carrier network |
sent | Accepted by the carrier for delivery |
delivered | Confirmed delivered to the recipient's handset |
failed | Delivery failed (invalid number, carrier rejection, etc.) |
Check delivery status via the portal SMS tab or by calling:
curl https://cliqtel.com/api/v1/sms/messages?direction=outbound \ -H "Authorization: Bearer YOUR_API_KEY"
List messages
Retrieve your SMS messages with optional filtering and pagination.
Query parameters
| Parameter | Type | Description |
|---|---|---|
direction | string | Filter by inbound or outbound |
status | string | Filter by status: queued, sent, delivered, failed |
number_id | integer | Filter by DID number ID |
from | string | Filter by sender number |
to | string | Filter by destination number |
since | datetime | Only messages after this timestamp (ISO 8601) |
page | integer | Page number (default: 1) |
per_page | integer | Results per page (default: 25, max: 100) |
curl https://cliqtel.com/api/v1/sms/messages?direction=outbound&per_page=10 \ -H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"id": 42,
"from": "+16625163513",
"to": "+31612345678",
"body": "Your order #1234 has shipped!",
"direction": "outbound",
"status": "delivered",
"segments": 1,
"cost": 0.008,
"currency": "EUR",
"carrier": "cliqtel",
"created_at": "2026-04-03T10:15:30+00:00"
}
],
"meta": {
"total": 156,
"page": 1,
"per_page": 10,
"total_pages": 16
}
}Get a message
Retrieve a single message by ID, including delivery status and cost.
curl https://cliqtel.com/api/v1/sms/messages/42 \ -H "Authorization: Bearer YOUR_API_KEY"
List SMS numbers
List your active numbers that have SMS enabled. Use the id field as
from_number_id when sending. A2P numbers are returned here too — identify them by
number_type, which returns a2p for A2P numbers.
curl https://cliqtel.com/api/v1/sms/numbers \ -H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{ "id": 11, "number": "+16625163513", "country_code": "US", "number_type": "local", "sms_enabled": true },
{ "id": 6, "number": "+16315950406", "country_code": "US", "number_type": "local", "sms_enabled": true }
]
}Delivery-status webhooks
Receive real-time delivery updates by providing a status_callback_url when sending a
message. This is distinct from the inbound-message webhook in
Step 5: delivery-status webhooks are receipts for your
outbound messages (queued → sent → delivered/failed), whereas the inbound webhook delivers
incoming replies from your contacts.
curl -X POST https://cliqtel.com/api/v1/sms/messages \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "+16625163513",
"to": "+31612345678",
"body": "Hello!",
"status_callback_url": "https://yourapp.com/webhooks/sms"
}'Cliqtel will POST to your URL when the message status changes:
{
"event": "sms.status_updated",
"data": {
"id": 42,
"from": "+16625163513",
"to": "+31612345678",
"direction": "outbound",
"status": "delivered",
"segments": 1,
"cost": 0.008,
"currency": "EUR",
"created_at": "2026-04-03T10:15:30+00:00",
"updated_at": "2026-04-03T10:15:33+00:00"
},
"timestamp": "2026-04-03T10:15:33+00:00"
}Verifying webhook signatures
Each webhook includes an X-Cliqtel-Signature header containing an HMAC-SHA256 signature of
the JSON payload, signed with your application key. Verify it before trusting any webhook.
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}Error handling
All errors return a JSON object with an error key:
{
"error": {
"code": "insufficient_balance",
"message": "Wallet balance too low to send this message"
}
}| HTTP Status | Error Code | Description |
|---|---|---|
| 401 | unauthenticated | Missing or invalid API key |
| 404 | not_found | Message or number not found |
| 422 | number_not_found | From number not found, inactive, or SMS not enabled |
| 422 | validation_error | Missing or invalid request parameters |
| 429 | rate_limited | Too many requests (see Rate Limits) |
| 500 | send_failed | Carrier rejected the message |
Rate limits
| Limit | Value |
|---|---|
| Requests per minute | 60 |
| Messages per second | 10 (carrier limit) |
Rate limit headers are included in every response:
X-RateLimit-Limit— maximum requests allowedX-RateLimit-Remaining— requests remaining in windowRetry-After— seconds to wait (only on 429 responses)
Code examples
Node.js
const API_KEY = 'your_api_key_here';
async function sendSms(from, to, body) {
const res = await fetch('https://cliqtel.com/api/v1/sms/messages', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ from, to, body }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error?.message || 'Send failed');
return data.data;
}
// Usage
const msg = await sendSms('+16625163513', '+31612345678', 'Hello!');
console.log(`Sent! ID: ${msg.id}, Status: ${msg.status}`);Python
import requests
API_KEY = 'your_api_key_here'
BASE_URL = 'https://cliqtel.com/api/v1/sms'
def send_sms(from_number, to, body):
resp = requests.post(
f'{BASE_URL}/messages',
headers={
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
},
json={'from': from_number, 'to': to, 'body': body},
)
resp.raise_for_status()
return resp.json()['data']
# Usage
msg = send_sms('+16625163513', '+31612345678', 'Hello from Python!')
print(f"Sent! ID: {msg['id']}, Status: {msg['status']}")PHP
$apiKey = 'your_api_key_here';
$ch = curl_init('https://cliqtel.com/api/v1/sms/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$apiKey}",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'from' => '+16625163513',
'to' => '+31612345678',
'body' => 'Hello from PHP!',
]),
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
echo "Sent! ID: {$data['data']['id']}\n";Webhook receiver (Node.js / Express)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/sms', (req, res) => {
const { event, data } = req.body;
if (event === 'sms.status_updated') {
console.log(`Message ${data.id}: ${data.status}`);
if (data.status === 'delivered') {
// Message confirmed delivered
} else if (data.status === 'failed') {
// Handle delivery failure
}
}
res.sendStatus(200);
});
app.listen(3000);A2P (two-way business messaging) numbers
For application-to-person messaging at scale — transactional alerts, reminders, 2FA, customer
service — we offer dedicated A2P numbers (e.g. the Netherlands +3197
range, plus UK, Belgium, Sweden, Poland and Australia). A2P numbers are messaging-only: they
carry SMS in both directions but no voice.
They use the same API as any SMS number — there are no A2P-specific endpoints. Send with
POST /api/v1/sms/messages, receive via the webhook above, and identify them in
GET /api/v1/sms/numbers by the number_type field, which returns
a2p for these numbers:
{
"id": 412,
"number": "+3197010279521",
"country_code": "NL",
"number_type": "a2p",
"sms_enabled": true
}Opt-out & STOP handling
Cliqtel enforces opt-out compliance automatically. When a recipient replies with a stop keyword
— STOP, STOPALL, UNSUBSCRIBE, CANCEL,
END, QUIT or OPT-OUT — we add them to your opt-out list and
block all further outbound messages to that number. A later START,
UNSTOP or OPT-IN clears it.
Attempts to message an opted-out recipient are rejected with HTTP 422 and
"opted_out": true in the response, so you never accidentally message someone who has left.
You remain responsible for obtaining prior consent before the first message.
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| "SMS not available for this number type" | The number's country/type combination doesn't support SMS | Try a mobile number instead, or check SMS-capable countries |
| "Number must be active to enable SMS" | Number is still provisioning or pending regulatory verification | Wait for the number to become Active before enabling SMS |
| "Insufficient wallet balance" | Wallet balance is below the SMS add-on monthly fee | Top up your wallet |
| Outbound SMS stuck in "queued" | Carrier processing delay | Wait up to 60 seconds. If still queued, check the number's messaging profile is assigned |
| Inbound SMS not appearing | Messaging profile webhook not configured | Contact support — we'll verify the carrier webhook setup |
| "Failed to send SMS" | Invalid destination number or carrier rejection | Verify the To number is valid E.164 format and the destination country accepts SMS |
FAQ
Can I send SMS to any country?
You can send outbound SMS to most countries worldwide. However, your sending number must be in a country that supports SMS. Some destinations may have higher delivery rates with local numbers.
What is a message segment?
A standard SMS segment is 160 characters using GSM-7 encoding (Latin alphabet). Messages using Unicode characters (emoji, Chinese, Arabic, etc.) are limited to 70 characters per segment. Longer messages are automatically split into multiple segments and reassembled on the recipient's phone.
Can I use SMS with the API only (no portal)?
Yes. The portal and API use the same underlying messaging system. You can send via API and view history in the portal, or use the API exclusively.
Is there a rate limit?
Default rate limit is 10 messages per second per account. Contact us for higher throughput if you need bulk messaging.
Do you support MMS (picture messages)?
MMS is on our roadmap and will be available on US and Canadian numbers first. Currently, only text SMS is supported.
Ready to start messaging?
Get an SMS-enabled number in under 60 seconds.