API documentation

Send WhatsApp messages from your own systems — order updates, one-time codes, reminders — and track delivery and read receipts. Every message goes through the same number protection as the dashboard: warm-up, human pacing, quiet hours and health checks.

Base URL

https://qonvo.falak.sh/api/v2

Format

JSON in, JSON out

Auth

Bearer API key

Authentication

Create a key under Settings → API keys and send it in the Authorization header. Keys start with qv_ and are shown once. Your plan must include API access.

curl https://qonvo.falak.sh/api/v2/me \
  -H "Authorization: Bearer qv_your_key"

Keep keys on your server

Never put a key in a mobile app or browser code. Revoke any key you think leaked — requests with it stop working immediately.

Laravel package

The qonvo/laravel-qonvo package adds a qonvo notification channel. It retries safely with an idempotency key per notification and turns API errors into exceptions.

Terminal
composer require qonvo/laravel-qonvo
OrderShipped.php
use Qonvo\Laravel\QonvoMessage;

public function via(object $notifiable): array
{
    return ['qonvo'];
}

public function toQonvo(object $notifiable): QonvoMessage
{
    return QonvoMessage::text("Your order #{$this->order->id} has shipped.");
}

// On the notifiable model
public function routeNotificationForQonvo(): string
{
    return $this->phone; // E.164, e.g. +218910000000
}

Send a message

POST/api/v2/messages

Queues a message and returns 202 Accepted. The pacer sends it as soon as the number's warm-up, caps and quiet hours allow, so status starts as queued. Replies to people who just wrote to you go first.

to required
Recipient phone in E.164 (+218910000000) or a group JID (…@g.us).
from optional
Sending number: its id, or its phone in E.164. Optional when your account has one number.
type optional
text (default), image, video, audio, document, sticker, location, contact, poll or template. See message types.
text
Message text, up to 4096 characters. Required for text.
category optional
transactional (default) or marketing. Marketing needs the contact's opt-in or a conversation they started, and is never sent at night.
scheduled_at optional
RFC 3339 time, up to 60 days ahead, like 2026-10-01T09:00:00Z.
curl -X POST https://qonvo.falak.sh/api/v2/messages \
  -H "Authorization: Bearer qv_your_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{"to": "+218920000000", "text": "Your order #1042 has shipped."}'
Response · 202 Accepted
{
  "data": {
    "id": "01928c5e-7a8b-7c3d-9e4f-5a6b7c8d9e0f",
    "status": "queued",
    "from": 3,
    "to": "+218920000000",
    "type": "text",
    "category": "transactional",
    "error": null,
    "created_at": "2026-09-30T10:15:00Z",
    "scheduled_at": null,
    "sent_at": null,
    "delivered_at": null,
    "read_at": null
  }
}

Message types

Set type and add the matching field. Media can be a public URL or base64 data.

image · video · audio · document · sticker
{"media": {"url": "https://…"}} or {"media": {"base64": "…", "mimetype": "image/png"}}, with optional filename and caption.
location
{"location": {"latitude": 32.88, "longitude": 13.19, "name": "Our shop"}}
contact
{"contact": {"name": "Support", "vcard": "BEGIN:VCARD…"}}
poll
{"poll": {"question": "…", "options": ["A", "B"], "selectable_count": 1}} — 2 to 12 options.
template
Official numbers only: {"template": {"name": "order_update", "language": "en", "components": […]}}
otp
{"otp": {"code": "482913", "expires_in_minutes": 10}} — a verification code with a Copy code button, sent first. Optional text must hold the code on its own line.
curl -X POST https://qonvo.falak.sh/api/v2/messages \
  -H "Authorization: Bearer qv_your_key" \
  -H "Content-Type: application/json" \
  -d '{"to": "+218920000000", "type": "image",
       "media": {"url": "https://example.com/receipt.png", "caption": "Your receipt"}}'

Get a message

GET/api/v2/messages/{id}

Returns the message with its current status — queued, sending, sent, delivered, read, failed or cancelled — and the time of each step. Failed messages carry the reason in error.

curl https://qonvo.falak.sh/api/v2/messages/01928c5e-7a8b-7c3d-9e4f-5a6b7c8d9e0f \
  -H "Authorization: Bearer qv_your_key"

List messages

GET/api/v2/messages

Messages you sent in the last 90 days, newest first. Pass next_cursor from the response as cursor to get the next page; it is null on the last page.

number_id optional
Only messages from this number.
status optional
Only messages with this status.
limit optional
1 to 100, default 25.
cursor optional
The next_cursor of the previous page.
curl "https://qonvo.falak.sh/api/v2/messages?status=failed&limit=50" \
  -H "Authorization: Bearer qv_your_key"

Account & quota

GET/api/v2/me

Your plan and how much of today's and this month's quota is used. A null limit means unlimited.

Response · 200 OK
{
  "data": {
    "id": 42,
    "name": "Demo Store",
    "plan": {"slug": "business", "name": "Business"},
    "quota": {"daily_limit": null, "daily_used": 40, "monthly_limit": null, "monthly_used": 1210}
  }
}

Numbers

GET/api/v2/numbers
GET/api/v2/numbers/{id}

Your numbers with their provider (whatsmeow for linked phones, cloud for official numbers), connection status and health from 0 to 100. Below 70 Qonvo sends more slowly; below 25 it pauses the number for a few hours.

Response · 200 OK
{
  "data": [
    {"id": 3, "phone": "+218910000000", "provider": "whatsmeow", "status": "connected", "health": 92}
  ]
}

Idempotency

Send an Idempotency-Key header (up to 255 characters) on every POST. If you repeat a request with the same key within 24 hours, you get the original response back and nothing is sent twice — so retrying after a timeout is always safe. Reusing a key with a different body returns 409 idempotency_conflict.

Good keys come from your own data: order-1042-shipped, otp-{user}-{attempt}.

Rate limits

Sending
500 requests per minute and 10,000 per hour per account.
Everything else
120 requests per minute per account.
Plan quota
Your plan's daily and monthly message limits, shown by GET /me.

Over a limit you get 429 with rate_limited and retry_after in seconds, or quota_exceeded with your current quota. Rate limits are separate from number pacing: accepted messages are always queued and sent at a safe speed.

Official numbers

Official numbers use Meta's WhatsApp Cloud API. WhatsApp only allows free-form messages to someone who wrote to you in the last 24 hours; outside that window, send an approved template. A free-form message outside the window returns 422 outside_window.

A linked number can route to an official one automatically when it is unhealthy, out of today's warm-up budget, or messaging a new contact — set it up on the number's page. Routed messages report the official number in from.

Errors

Errors share one shape. fields is only present for validation errors.

Response · 422 Unprocessable Entity
{
  "error": {
    "code": "validation_failed",
    "message": "The given data was invalid.",
    "fields": {"to": ["The to field is required."]}
  }
}
401 unauthenticated
Missing, wrong or revoked key.
403 forbidden
Your plan has no API access, or the resource is not yours.
404 not_found
No such message, number or endpoint.
409 idempotency_conflict
Key reused with a different body.
422
validation_failed, number_not_connected, recipient_suppressed (opted out, or marketing without consent), outside_window.
429
rate_limited or quota_exceeded.
5xx server_error
Retry with backoff and the same idempotency key.

Legacy API (v1)

Integrations built for the previous Qonvo API keep working unchanged at https://qonvo.falak.sh/api/whatsapp/…, with the same keys, requests and responses. New integrations should use v2 for idempotency, scheduling and consent.

POST /messages/send
Send a message.
GET /messages · /messages/{id}
Message history and status.
/numbers …
List, create, connect, check status and delete numbers.
/groups …
List, sync and get invite links for groups.