Overview
The Rashbery WhatsApp Gateway provides a REST API for sending text messages, media, and group messages through your connected WhatsApp devices. It acts as a secure proxy between your application and the underlying WhatsApp bot service.
How It Works
- Add a device: Go to Devices, create a device and scan the QR code with WhatsApp.
- Get a token: Open the device detail page or call
POST /wsr/api-tokenswithdevice_idto create a device-scoped Bearer token. - Send messages: Call endpoints with only
Authorization: Bearer <token>. Nodevice_idis required in the body when the token is device-scoped. - Check status: Each send returns a
request_id. Use the Requests endpoint to look up delivery status.
Key Features
- Token-based auth: Secure Bearer token authentication via Laravel Sanctum (hashed at rest)
- Device-scoped tokens: Tokens bind to one device so body
device_idis not needed - Request tracking: Every message recorded with
pending → sent/delivered/read/failedstatus - Subscription gated: Access requires active subscription or free-tier quota
- Auto-cleanup: Request history auto-deleted after 3 days
- Rate limited: 30 requests/min per API token
Authentication
All API endpoints require a Bearer token in the Authorization header. Prefer device-scoped tokens (created with device_id) so send endpoints only need the token — no device_id in the body.
Security notes:
- Tokens are stored as SHA-256 hashes; only the plain token is shown once at creation.
- Device-scoped tokens can only operate on the bound device (ownership is verified).
- Revoke compromised tokens immediately via
DELETE /wsr/api-tokens/{id}. - Always send over HTTPS in production; never commit tokens to source control.
| Method | Token Type | Where to Get It |
|---|---|---|
Authorization: Bearer <token> | Laravel Sanctum Token | Device detail page or POST /wsr/api-tokens with device_id |
Headers
Authorization: Bearer 8Xk2m9...Vd3j
Content-Type: application/jsonSubscription & Quota
- Sending messages requires an active subscription or free-tier quota.
- When your subscription expires, API access is blocked with a
403response. - Free-tier users get a limited message quota; once exhausted, subscribe to a paid plan.
- The
/userendpoint can be called without subscription check to query your quota.
Messaging
Send text messages, check if a number is on WhatsApp, and broadcast to multiple recipients.
Send a text message to a WhatsApp number
Request Body
{
"number": "628123456789",
"message": "Hello from Rashbery WhatsApp Gateway!"
}Response
{
"status": true,
"message": "Message sent successfully",
"data": {
"request_id": 42,
"status": "sent",
"wa_message_id": "3EB0F7D8A1B2C3D4E5F6"
}
}Send the same text message to multiple numbers
Request Body
{
"numbers": [
"628123456789",
"628987654321"
],
"message": "Broadcast message",
"delay_ms": 1000
}Response
{
"status": true,
"message": "Bulk messages sent",
"data": {
"request_ids": [
42,
43
],
"status": "sent"
}
}Check whether a phone number is registered on WhatsApp
Request Body
{
"number": "628123456789"
}Response
{
"status": true,
"message": "Number check complete",
"data": {
"isRegistered": true
}
}Send media (image, video, document) from a public URL
Request Body
{
"number": "628123456789",
"file_url": "https://example.com/invoice.pdf",
"caption": "Your invoice is ready"
}Response
{
"status": true,
"message": "Media sent successfully",
"data": {
"request_id": 44,
"status": "sent",
"wa_message_id": "3EB0F7D8A1B2C3D4E5F6"
}
}Send media from a base64-encoded string
Request Body
{
"number": "628123456789",
"base64": "/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAA...",
"mimetype": "image/jpeg",
"filename": "photo.jpg",
"caption": "Photo from API"
}Response
{
"status": true,
"message": "Media sent successfully",
"data": {
"request_id": 45,
"status": "sent",
"wa_message_id": "3EB0F7D8A1B2C3D4E5F6"
}
}Media
Send images, videos, documents, and audio files via URL or base64 encoding.
Groups
Send text messages and media to WhatsApp groups. Provide group ID or group name.
Send a text message to a WhatsApp group using group ID
Request Body
{
"group_id": "120363024567890@g.us",
"message": "Hello everyone!"
}Response
{
"status": true,
"message": "Group message sent",
"data": {
"request_id": 46,
"status": "sent",
"wa_message_id": "3EB0F7D8A1B2C3D4E5F6"
}
}Send media to a WhatsApp group
Request Body
{
"group_id": "120363024567890@g.us",
"file_url": "https://example.com/promo.jpg",
"caption": "Check out this offer!",
"mention": "628123456789"
}Response
{
"status": true,
"message": "Group media sent",
"data": {
"request_id": 47,
"status": "sent",
"wa_message_id": "3EB0F7D8A1B2C3D4E5F6"
}
}Request Status
Track every message you send. Each API call creates a request record with status updates (pending → sent / delivered / read / failed). Records are auto-purged after 3 days.
List your recent message requests (last 3 days) with delivery status
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| device_id | integer | No | Filter by device |
| status | string | No | Filter: pending / sent / delivered / read / failed |
| channel | string | No | Filter: message / media / group / group-media |
| per_page | integer | No | Items per page (max 100, default 20) |
Response
{
"status": true,
"data": {
"data": [
{
"id": 42,
"channel": "message",
"to": "628123456789",
"body": "Hello!",
"status": "sent",
"wa_message_id": "3EB0...7F",
"sent_at": "2026-07-21T15:30:00.000Z",
"created_at": "2026-07-21T15:30:00.000Z"
}
],
"current_page": 1,
"last_page": 3,
"per_page": 20,
"total": 48
}
}Get detailed status of a single message request
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | Yes | Request ID from the list |
Response
{
"status": true,
"data": {
"id": 42,
"user_id": 5,
"channel": "message",
"direction": "out",
"to": "628123456789",
"body": "Hello!",
"status": "sent",
"wa_message_id": "3EB0F7D8A1B2C3D4E5F6",
"error_message": null,
"sent_at": "2026-07-21T15:30:00.000Z",
"created_at": "2026-07-21T15:30:00.000Z"
}
}Error Codes
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request successful |
| 400 | Bad Request | Invalid request body or parameters |
| 401 | Unauthorized | Missing or invalid Bearer token |
| 403 | Forbidden | Subscription expired, quota exhausted, or insufficient permissions |
| 404 | Not Found | Device or resource not found |
| 422 | Validation Error | Request body validation failed |
| 429 | Too Many Requests | Rate limit exceeded (30 req/min for API) |
| 503 | Service Unavailable | Device not connected or wa-bot service unreachable |
Error Response Format
{
"status": false,
"message": "Device is not connected. Please scan the QR code first.",
"data": {
"request_id": 42,
"status": "failed"
}
}Rate Limits
| Scope | Limit | Description |
|---|---|---|
| External API | 30 requests per minute per user | All /wsr/* endpoints share this limit |
| Auth endpoints | 5 attempts per minute per email+IP | Login, register, password reset |
| Coupon validation | 10 requests per minute per user | Coupon apply endpoint |
429 Response
{
"status": false,
"message": "Too many requests. Please slow down."
}When receiving 429, wait at least 2 seconds before retrying.
SDKs & Code Examples
Curl
curl -X POST https://ws.rashbery.com/wsr/api-tokens \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "My App", "abilities": ["*"], "device_id": 1}'Javascript
const axios = require('axios');
const api = axios.create({
baseURL: 'https://ws.rashbery.com/wsr',
headers: {
'Authorization': 'Bearer YOUR_API_TOKEN',
'Content-Type': 'application/json'
}
});
async function createToken(name) {
const response = await api.post('/api-tokens', { name, abilities: ['*'], device_id: 1 });
console.log('Save this token — it will not be shown again:', response.data.data.token);
return response.data.data;
}Php
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://ws.rashbery.com/wsr/',
'headers' => [
'Authorization' => 'Bearer YOUR_API_TOKEN',
'Content-Type' => 'application/json'
]
]);
$response = $client->post('message/send', [
'json' => [
'number' => '628123456789',
'message' => 'Hello from PHP!'
]
]);
$data = json_decode($response->getBody(), true);
echo "Request ID: " . $data['data']['request_id'] . PHP_EOL;Python
import requests
API_URL = 'https://ws.rashbery.com/wsr'
API_TOKEN = 'YOUR_API_TOKEN'
headers = {
'Authorization': f'Bearer {API_TOKEN}',
'Content-Type': 'application/json'
}
def send_message(number, message):
response = requests.post(
f'{API_URL}/message/send',
headers=headers,
json={
'number': number,
'message': message
}
)
return response.json()
# Usage
result = send_message('628123456789', 'Hello from Python!')
print(f"Request ID: {result['data']['request_id']}, Status: {result['data']['status']}")