make setup && make migrate-up && make runGet credentials from .env:
CLIENT_ID=$(grep '^CLIENT_ID=' .env | cut -d'=' -f2)
CLIENT_SECRET=$(grep '^CLIENT_SECRET=' .env | cut -d'=' -f2)See SECURITY.md for production configuration.
- Token/Credential Management - Basic Auth with
CLIENT_ID:CLIENT_SECRET - Device/Webhook Operations - Bearer Auth with Matrix token (
mt_xxxxx)
Tokens are Matrix authentication tokens that provide access to device and webhook operations. Each token is associated with the credential that created it.
First token is auto-marked as admin and creates the host Matrix identity:
curl -X POST http://localhost:8080/api/v1/tokens \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{"use_host": false}'Response:
{
"message": "Matrix token created successfully",
"token": "mt_abc123..."
}Shares the admin's Matrix credentials and any linked devices:
curl -X POST http://localhost:8080/api/v1/tokens \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{"use_host": true}'Creates new Matrix credentials:
curl -X POST http://localhost:8080/api/v1/tokens \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{"use_host": false, "expires_at": "2026-12-31T23:59:59Z"}'Note
use_host: true: Token can access admin's linked devicesuse_host: false: Token has its own empty device list, must link devices separately- Tokens are automatically associated with the credential used to create them
View all tokens associated with your credential:
curl -X GET http://localhost:8080/api/v1/tokens \
-u "$CLIENT_ID:$CLIENT_SECRET"Super admin credentials can see all tokens they created plus legacy tokens (those without a credential_id).
curl -X DELETE http://localhost:8080/api/v1/tokens/{token_id} \
-u "$CLIENT_ID:$CLIENT_SECRET"Replace {token_id} with the token ID from the List Tokens response.
Set your token:
TOKEN="mt_abc123..."Requests device addition and returns QR code WebSocket URL:
curl -X POST http://localhost:8080/api/v1/devices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"platform": "wa"}'Response:
{
"message": "Scan the QR code to link your device",
"qr_code_url": "ws://localhost:8080/api/v1/devices/qr-code?token=mt_abc123..."
}Connect to WebSocket to receive QR code for device linking:
websocat "ws://localhost:8080/api/v1/devices/qr-code?token=$TOKEN"curl -X GET http://localhost:8080/api/v1/devices \
-H "Authorization: Bearer $TOKEN"Response:
[
{
"platform": "wa",
"device_id": "237123456789"
}
]Send messages to individual contacts or groups. At least one of contact or group_url must be provided.
curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact": "1234567890",
"platform": "wa",
"text": "Hello from API"
}'Send the same message to multiple addresses by providing a comma-separated list:
curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact": "1234567890, 0987654321, another_address",
"platform": "wa",
"text": "Hello to multiple recipients"
}'If you don't have the group ID, use the group invite URL. You can also provide multiple group URLs comma-separated:
curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"group_url": "https://chat.whatsapp.com/invite123",
"platform": "wa",
"text": "Hello group!"
}'Multiple groups:
curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"group_url": "https://chat.whatsapp.com/invite123, https://chat.whatsapp.com/invite456",
"platform": "wa",
"text": "Hello to multiple groups!"
}'Use reply_id to reply to a specific message (works with both contacts and groups):
curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"contact": "1234567890",
"platform": "wa",
"text": "This is a reply",
"reply_id": "$eCLf_5pzs8ZBhEs5ZojVRS0K3SBLf73cHs2q_Je4kfQ"
}'Or reply in a group:
curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"group_url": "https://chat.whatsapp.com/invite123",
"platform": "wa",
"text": "This is a reply in group",
"reply_id": "$eCLf_5pzs8ZBhEs5ZojVRS0K3SBLf73cHs2q_Je4kfQ"
}'curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-F "contact=1234567890" \
-F "platform=wa" \
-F "text=Check out this document" \
-F "file=@/path/to/document.pdf"For multiple recipients with files, use comma-separated addresses:
curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-F "contact=1234567890, 0987654321" \
-F "platform=wa" \
-F "text=Check out this document" \
-F "file=@/path/to/document.pdf"Send file to a group:
curl -X POST http://localhost:8080/api/v1/devices/237123456789/message \
-H "Authorization: Bearer $TOKEN" \
-F "group_url=https://chat.whatsapp.com/invite123" \
-F "platform=wa" \
-F "text=Check out this document" \
-F "file=@/path/to/document.pdf"File must have an extension.
Note
- If both
contactandgroup_urlare provided,group_urltakes priority - The
reply_idcan be obtained from webhook payloads for incoming messages - Both
contactandgroup_urlsupport comma-separated values for multiple recipients
curl -X DELETE http://localhost:8080/api/v1/devices \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"device_id": "237123456789",
"platform": "wa"
}'Receive incoming message notifications via HTTP POST.
Important
Webhooks are tied to tokens. Every device stored under a token will publish incoming messages to that token's configured webhook. If multiple devices share the same token, all their incoming messages will be delivered to the same webhook URL.
curl -X POST http://localhost:8080/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-server.com/webhook"}'Response:
{
"id": 1,
"url": "https://your-server.com/webhook",
"active": true,
"created_at": "2026-04-27T10:00:00Z",
"updated_at": "2026-04-27T10:00:00Z"
}When an incoming message is received, your webhook URL will receive a POST request with the following JSON structure:
{
"id": "$A-XXXXXXXXXXXXXXXXXXXXXXX",
"is_contact": false,
"type": "m.text",
"from": "@whatsapp_2376987654321:matrix.example.com",
"to": "@xxxxxxxxxxxx:matrix.example.com",
"message": "Good morning",
"device_id": "237123456789",
"media": {
"content": null,
"info": {
"size": 0,
"mime_type": "",
"width": 0,
"height": 0,
"blur_hash": ""
}
}
}Field descriptions:
id: Unique Matrix event ID for the messageis_contact:trueif message is from a saved contact,falseif from unknown sendertype: Matrix message type (e.g., "m.text", "m.image", "m.file", "m.video")from: Sender's Matrix user ID or contact nameto: Recipient's Matrix user IDmessage: Text content of the message (empty for media-only messages)device_id: Device identifier that received the messagemedia: Media object containing binary data and metadata (null if no media)content: Base64 encoded or raw binary media datainfo: Media metadatasize: File size in bytesmime_type: MIME type of the mediawidth: Image/video width (0 if not applicable)height: Image/video height (0 if not applicable)blur_hash: BlurHash string for image preview (used by some clients)
curl -X GET http://localhost:8080/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN"curl -X PUT http://localhost:8080/api/v1/webhooks/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://new-url.com/webhook", "active": false}'Fields are optional. Omit to keep current value.
curl -X DELETE http://localhost:8080/api/v1/webhooks/1 \
-H "Authorization: Bearer $TOKEN"Manage API client credentials. Requires Basic Auth with admin credentials. Each credential can create and manage its own tokens.
curl -X POST http://localhost:8080/api/v1/credentials \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"client_id": "my-app",
"description": "Production API client"
}'Response:
{
"message": "Credential created successfully",
"credential": {
"client_id": "my-app",
"role": "user",
"scopes": [],
"description": "Production API client",
"active": true,
"created_at": "2026-04-27T10:00:00Z",
"updated_at": "2026-04-27T10:00:00Z"
},
"client_secret": "xxxxxxxxxxxx"
}Important
Save client_secret immediately. It's only shown once.
curl -X GET http://localhost:8080/api/v1/credentials \
-u "$CLIENT_ID:$CLIENT_SECRET"Update credential properties like description, active status, or scopes:
curl -X PUT http://localhost:8080/api/v1/credentials/my-app \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated description",
"active": true,
"scopes": ["tokens:read:*", "tokens:write:create", "tokens:write:delete"]
}'Fields are optional. Omit to keep current value.
curl -X DELETE http://localhost:8080/api/v1/credentials/my-app \
-u "$CLIENT_ID:$CLIENT_SECRET"Cannot delete super admin credentials.
Note
- Each credential can create and manage its own tokens via the
/api/v1/tokensendpoints - Tokens are automatically associated with the credential that created them
- Deleting a credential will affect all tokens created by that credential
Swagger UI: http://localhost:8080/docs/index.html