Quickstart
In 5 steps you'll have an instance connected and sending messages.
- Get API key — Contact sales to obtain your tenant credentials.
- Create instance —
POST /instance/createwith the name and your webhook URL. - Scan QR —
GET /instance/:name/qrand scan the code with your phone. - Wait for connection —
GET /instance/:name/statusuntil you receivestate: "open". - Send first message —
POST /message/text.
# 1. Create instance
curl -X POST https://wa.sinapsia.com.ar/instance/create \
-H "apikey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"instanceName":"acme__soporte__main","webhookUrl":"https://my-saas.com/webhooks/whatsapp","webhookSecret":"my-secure-secret-32"}'
# 2. Get QR (base64 image)
curl https://wa.sinapsia.com.ar/instance/acme__soporte__main/qr \
-H "apikey: YOUR_API_KEY"
# 3. Check status (wait for state: "open")
curl https://wa.sinapsia.com.ar/instance/acme__soporte__main/status \
-H "apikey: YOUR_API_KEY"
# 4. Send first message
curl -X POST https://wa.sinapsia.com.ar/message/text \
-H "apikey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"instanceName":"acme__soporte__main","to":"5491112345678","text":"Hello! This is my first message.","lane":"normal"}'
Authentication
All requests (except GET /health) require the apikey header.
apikey: YOUR_API_KEY
| Header | Access |
|---|---|
apikey: <your-api-key> |
Only instances and resources belonging to your tenant |
Instance naming
All instances follow this format:
{tenantId}__{clientId}__{label}
Example:
acme__soporte__main
__). The system validates this format before creating any instance.
Lanes (Anti-ban)
Every message is sent through one of three lanes. The lane controls the delay and send limits to protect the WhatsApp number.
| Lane | Delay | Limit/hour | Limit/day | Use for |
|---|---|---|---|---|
urgent |
1–2s | No limit | No limit | OTP, critical alerts |
normal |
3–8s | 80 msg | 500 msg | Conversations, notifications |
bulk |
8–15s | 30 msg | 200 msg | Mass campaigns |
- Limits are per instance, not per tenant.
urgentis always processed beforenormalorbulk.- Messages that exceed the limit wait in queue, they are not discarded.
- Default if not specified:
normal.
Endpoints — Instances
close state until a number is linked by scanning the QR.{
"instanceName": "acme__soporte__main",
"webhookUrl": "https://my-saas.com/webhooks/whatsapp",
"webhookSecret": "secret-minimum-16-characters"
}
201 Created
{ "instanceName": "acme__soporte__main", "status": "close" }
400 body validation · 409 instance already exists
{ "qrcode": "data:image/png;base64,iVBORw0KGgo..." }
{ "phoneNumber": "5491112345678" }
200 OK
{ "pairingCode": "123456" }
{
"instanceName": "acme__soporte__main",
"state": "open",
"phoneNumber": "5491112345678",
"connectedAt": "2026-03-15T10:00:00.000Z",
"messagesQueuedCount": 0
}
| state | Description |
|---|---|
close | Not linked. Requires QR or pairing code. |
connecting | QR scanned. Waiting for confirmation. |
open | Connected and ready to send messages. |
{ "device": "iPhone 12", "platform": "WHATSAPP" }
[
{ "instanceName": "acme__soporte__main", "state": "open", "phoneNumber": "5491112345678" },
{ "instanceName": "acme__ventas__main", "state": "close", "phoneNumber": null }
]
// Array of tenant instances
// Array of client instances
{ "urgent": 0, "normal": 3, "bulk": 12, "total": 15 }
Endpoints — Messages
202 Accepted immediately. The message is queued and sent after the anti-ban delay corresponding to the lane.
Shared response (all send endpoints)
{
"id": "3b2e1f4a-8c9d-4e2f-b7a1-0d5c6e3f9b2a",
"status": "pending",
"lane": "normal",
"queuedAt": "2026-03-15T10:30:00.000Z"
}
text field supports spintax.{
"instanceName": "acme__soporte__main",
"to": "5491112345678",
"text": "{Hello|Hey}! Your code is 1234.",
"lane": "normal"
}
to: international number without + or spaces. lane is optional, default normal.caption is optional and supports spintax.{
"instanceName": "acme__soporte__main",
"to": "5491112345678",
"imageUrl": "https://cdn.example.com/photo.jpg",
"caption": "Check this out",
"lane": "normal"
}
{
"instanceName": "acme__soporte__main",
"to": "5491112345678",
"documentUrl": "https://cdn.example.com/invoice.pdf",
"fileName": "invoice-001.pdf",
"caption": "Your invoice attached",
"lane": "normal"
}
{
"instanceName": "acme__soporte__main",
"to": "5491112345678",
"audioUrl": "https://cdn.example.com/audio.ogg",
"lane": "normal"
}
caption is optional.{
"instanceName": "acme__soporte__main",
"to": "5491112345678",
"videoUrl": "https://cdn.example.com/video.mp4",
"caption": "Watch this",
"lane": "normal"
}
instanceName to indicate which number was chosen.{
"tenantId": "acme",
"clientId": "soporte",
"to": "5491112345678",
"type": "text",
"text": "Hello!",
"lane": "normal"
}
type is required: text | image | document | audio | video. Each type requires its own additional specific fields.{
"id": "3b2e1f4a-8c9d-4e2f-b7a1-0d5c6e3f9b2a",
"instanceName": "acme__soporte__main",
"lane": "normal",
"to": "5491112345678",
"type": "text",
"status": "sent",
"createdAt": "2026-03-15T10:30:00.000Z",
"sentAt": "2026-03-15T10:30:07.000Z",
"error": null
}
| status | Description |
|---|---|
| pending | In queue, waiting to be sent |
| sent | Successfully sent |
| failed | Error — see error field |
Webhooks
When creating an instance a webhookUrl is provided. The gateway sends a POST to that URL each time a relevant event occurs.
HMAC signature verification
Each request includes the x-gateway-signature header with an HMAC-SHA256 hex digest of the raw body, signed with your webhookSecret. Always verify the signature before processing.
const crypto = require('crypto')
function verifySignature(rawBody, secret, signature) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
return expected === signature
}
import hmac, hashlib
def verify_signature(raw_body: bytes, secret: str, signature: str) -> bool:
expected = hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return expected == signature
Retry policy
The gateway retries 3 times: delays of 1s, 5s, 30s. Your endpoint must respond 2xx quickly.
Events
instance.connected / instance.disconnected
{
"event": "instance.connected",
"instanceName": "acme__soporte__main",
"data": {
"state": "open",
"reason": null
},
"timestamp": "2026-03-15T10:30:00.000Z"
}
message.received
{
"event": "message.received",
"instanceName": "acme__soporte__main",
"data": {
"from": "5491112345678",
"pushName": "Juan Pérez",
"messageType": "conversation",
"text": "Hello, I need help",
"messageId": "AC3EADB76DB5BE4130602FBF2039604A",
"timestamp": 1773596760
},
"timestamp": "2026-03-15T10:30:00.000Z"
}
textisnullfor media messages.- Only messages with
fromMe: falseare forwarded (not your own sent messages). messageType:conversation,imageMessage,documentMessage,audioMessage,videoMessage.
Monitoring
Endpoints to query statistics and manage the message queue for your instances.
{
"instances": 2,
"totals": { "queued": 5, "sent": 1250, "failed": 3 },
"perInstance": [
{
"instanceName": "acme__soporte__main",
"queue": { "urgent": 0, "normal": 5, "bulk": 2, "total": 7 },
"messages": { "sent": 450, "failed": 1, "pending": 7 }
}
]
}
{ "instanceName": "acme__soporte__main", "flushed": 12 }
GET /health
Health endpoint. No authentication required. Useful for monitors and load balancers.
{
"status": "ok",
"tenants": 1,
"instances": 2,
"queuedMessages": 5,
"uptime": 3600
}
Errors
| Status | Meaning | Action |
|---|---|---|
400 |
Invalid body | Check the issues field in the response |
401 |
Missing or invalid API key | Verify the apikey header |
403 |
No access to this resource | The instanceName must start with your tenantId |
404 |
Instance or message not found | Verify the name or ID |
409 |
Instance already exists | Use the existing one or choose a different name |
502 |
Error in the WhatsApp service | Check WhatsApp session; retry |
500 |
Internal error | Report to the operator |
400 error format
{
"error": "Validation error",
"issues": [
{
"path": ["instanceName"],
"message": "Must follow {tenant}__{client}__{label} format"
}
]
}
Spintax
Spintax allows generating text variants to avoid repetitive messages. It is resolved randomly at send time.
Syntax: {option1|option2|option3} — supports nesting.
# Simple
{Hello|Hey|Hi}! {How are you|What's up}?
→ "Hey! What's up?"
# Nested
{Good {morning|afternoon}|Hello}!
→ "Good afternoon!"
text field (in /message/text) · caption field (in /message/image, /message/document, /message/video)
Quick reference
| Field | Format | Example |
|---|---|---|
instanceName |
{tenant}__{client}__{label} |
acme__soporte__main |
to |
International number without + |
5491112345678 |
lane |
urgent / normal / bulk |
normal |
type (route) |
text / image / document / audio / video |
text |
webhookSecret |
String, min. 16 characters | my-secure-secret-32 |
| Timestamps | ISO 8601 UTC | 2026-03-15T10:30:00.000Z |
| Message IDs | UUID v4 | 3b2e1f4a-... |
Integrations
Ready-to-use configuration for the most popular platforms. All examples send a text message via POST /message/text — the same pattern applies to all other endpoints.
n8n
Add an HTTP Request node and configure it as follows:
Method: POST
URL: https://wa.sinapsia.com.ar/message/text
Auth: Header Auth → Name: apikey / Value: {{ $vars.WA_API_KEY }}
Body: JSON
{
"instanceName": "acme__support__main",
"to": "{{ $json.phone }}",
"text": "{{ $json.message }}",
"lane": "normal"
}
Make (ex-Integromat)
Module HTTP → Make a request:
URL: https://wa.sinapsia.com.ar/message/text
Method: POST
Headers: apikey: YOUR_API_KEY
Body type: Raw
Content type: application/json
Request body:
{
"instanceName": "acme__support__main",
"to": "{{1.phone}}",
"text": "{{1.message}}",
"lane": "normal"
}
Zapier
Action Webhooks by Zapier → POST:
URL: https://wa.sinapsia.com.ar/message/text
Payload Type: JSON
Data:
instanceName acme__support__main
to (phone field from trigger)
text (message field from trigger)
lane normal
Headers:
apikey YOUR_API_KEY
Power Automate
Add an HTTP action to your flow:
Method: POST
URI: https://wa.sinapsia.com.ar/message/text
Headers:
apikey @{variables('WA_API_KEY')}
Content-Type application/json
Body:
{
"instanceName": "acme__support__main",
"to": "@{triggerBody()?['phone']}",
"text": "@{triggerBody()?['message']}",
"lane": "normal"
}
Pipedream
Add a Node.js code step:
import axios from "axios"
export default defineComponent({
async run({ steps, $ }) {
const { data } = await axios.post(
"https://wa.sinapsia.com.ar/message/text",
{
instanceName: "acme__support__main",
to: steps.trigger.event.phone,
text: steps.trigger.event.message,
lane: "normal",
},
{ headers: { apikey: process.env.WA_API_KEY } }
)
return data
},
})
Home Assistant
Add to configuration.yaml. Store the API key in secrets.yaml.
# configuration.yaml
rest_command:
send_whatsapp:
url: https://wa.sinapsia.com.ar/message/text
method: POST
headers:
apikey: !secret wa_api_key
Content-Type: application/json
payload: >
{"instanceName":"acme__support__main",
"to":"{{ to }}",
"text":"{{ message }}",
"lane":"normal"}
content_type: application/json
service: rest_command.send_whatsapp and data: {to: "1...", message: "Hello!"}
Your backend
// native fetch (Node.js 18+)
const res = await fetch("https://wa.sinapsia.com.ar/message/text", {
method: "POST",
headers: {
"apikey": process.env.WA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
instanceName: "acme__support__main",
to: "15551234567",
text: "Hello from Node.js!",
lane: "normal",
}),
})
const data = await res.json()
import httpx, os
res = httpx.post(
"https://wa.sinapsia.com.ar/message/text",
headers={"apikey": os.environ["WA_API_KEY"]},
json={
"instanceName": "acme__support__main",
"to": "15551234567",
"text": "Hello from Python!",
"lane": "normal",
},
)
data = res.json()
// Guzzle HTTP
$client = new \GuzzleHttp\Client();
$res = $client->post('https://wa.sinapsia.com.ar/message/text', [
'headers' => ['apikey' => getenv('WA_API_KEY')],
'json' => [
'instanceName' => 'acme__support__main',
'to' => '15551234567',
'text' => 'Hello from PHP!',
'lane' => 'normal',
],
]);
$data = json_decode($res->getBody(), true);
import (
"bytes"; "encoding/json"; "net/http"; "os"
)
payload, _ := json.Marshal(map[string]string{
"instanceName": "acme__support__main",
"to": "15551234567",
"text": "Hello from Go!",
"lane": "normal",
})
req, _ := http.NewRequest("POST", "https://wa.sinapsia.com.ar/message/text", bytes.NewBuffer(payload))
req.Header.Set("apikey", os.Getenv("WA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)