التواصل متعدد القنوات
English
العربية
English
العربية
أرسل الرسائل، وأدرِ جهات الاتصال، وشغّل الحملات، وتلقّى الأحداث الفورية عبر جميع القنوات من أي لغة برمجة.
تستخدم واجهة برمجة التطبيقات v1 مصادقة زوج المفاتيح. يجب أن يتضمن كل طلب ترويستي HTTP مخصّصتين. قم بإنشاء مفاتيحك من مفاتيح API.
X-Public-Key: pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Secret-Key: sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
المفتاح العام
يحدّد مساحة العمل الخاصة بك. البادئة: pk_.
آمن للتسجيل.
المفتاح السري
يُوقّع الطلبات. البادئة: sk_.
يُعرض مرة واحدة فقط — احفظه في مدير أسرار.
يمكن حصر مفاتيح API في قدرات محددة وربطها اختيارياً بجلسة واتساب واحدة. الطلبات بدون القدرة المطلوبة تتلقى HTTP 403
insufficient_ability.
| Ability | Grants access to |
|---|---|
| * | Full access to all endpoints (wildcard). |
| sessions.read | List and inspect connected sessions and bots. |
| messages.send | POST /api/v1/messages/send. |
| messages.read | GET /api/v1/messages and related read-only endpoints. |
| contacts.read | List and view contacts, groups, and tags. |
| contacts.write | Create and update contacts, groups, and tags. |
| campaigns.read | List and view campaigns, templates, and analytics. |
| campaigns.write | Create, update, start and pause campaigns and automations. |
| webhooks.read | List and view webhooks and delivery logs. |
| webhooks.write | Create, update, delete and test webhooks. |
كل مفتاح API لديه ثلاثة حدود مستقلة. يمكن تكوين تجاوزات لكل مفتاح عند إنشاء المفتاح — وإلا فإن الافتراضات العامة للمستأجر أدناه تنطبق.
| النطاق | الافتراضي | On breach |
|---|---|---|
| Per-minute burst | 429 rate_limit_exceeded | |
| Daily quota | 429 daily_quota_exceeded | |
| Monthly quota | 429 monthly_quota_exceeded |
كل استجابة مصادقة تتضمن رؤوس تعرض استخدام الحصة الحالية. عند إرجاع 429، Retry-After يشير الرأس إلى عدد الثواني التي يجب الانتظارها.
| Header | المعنى |
|---|---|
| X-RateLimit-Limit | Per-minute request ceiling for this key. |
| X-RateLimit-Daily-Limit | Daily request quota for this key. |
| X-RateLimit-Daily-Remaining | Requests still available today. |
| Retry-After | Seconds to wait before retrying (only on 429 responses). |
Safely retry mutating requests without the fear of creating duplicates. Attach an
Idempotency-Key
header to any POST, PUT, PATCH or DELETE call — if the same key arrives again within 24 hours on the same endpoint, the original status code and response body are replayed verbatim.
curl -X POST https://zagely.com/api/v1/messages/send \
-H "X-Public-Key: pk_your_public_key" \
-H "X-Secret-Key: sk_your_secret_key" \
-H "Idempotency-Key: 5b2f9e3c-7b8d-4a1e-9f1b-12ab34cd56ef" \
-H "Content-Type: application/json" \
-d '{"channel":"whatsapp","to":"+15551234567","message":"hi"}'
| Rule | القيمة |
|---|---|
| Applies to | POST, PUT, PATCH, DELETE on /api/v1/* mutating endpoints. |
| Character set | A–Z, a–z, 0–9, hyphen and underscore. Max 255 chars. |
| TTL | 24 hours from the first accepted response. |
| Scope | Per API key + request path. Different keys see different results. |
| Replay marker | Replies add Idempotent-Replay: true header. |
| Malformed key | 400 invalid_idempotency_key. |
Utility endpoints for monitoring and introspecting your API key.
/api/v1/health
Unauthenticated liveness probe. Returns 200 when the database is reachable, 503 otherwise.
{ "data": { "status": "ok", "timestamp": "2026-04-22T12:00:00Z" } }
/api/v1/me
Returns the current API key with its abilities, bound session, per-key limits, and today / this-month usage counters.
{
"data": {
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"name": "Production Key",
"tenant_id": "...",
"session_id": null,
"abilities": ["messages.send", "contacts.read"],
"expires_at": null,
"last_used_at": "2026-04-22T11:59:02Z",
"usage": { "today": 142, "month": 3_812 },
"limits": { "daily": 10000, "monthly": 250000, "per_minute": 120 },
"remaining": { "daily": 9858 }
}
}
Every API call that creates or sends a message requires a channel
field that tells the platform which messaging channel to use. Each channel also has its own "connection" concept referenced by
session_id.
| channel value | session_id refers to | session_id required? | Recipient field |
|---|---|---|---|
| WhatsApp session UUID | to (phone number) | ||
| telegram | Telegram bot UUID | contact_id or telegram_chat_id | |
| messenger | Messenger page UUID | contact_id (has messenger_psid) | |
| viber | Viber bot UUID | contact_id (has viber_user_id) | |
| discord | Discord bot UUID | contact_id (has discord_user_id) | |
| Instagram account UUID | contact_id (has instagram_igsid) | ||
| webpush | Push subscriber UUID | contact_id (subscribed) | |
| — (not applicable) | contact_id (has email) |
The v1 API currently supports whatsapp
as the channel.
Additional channels are available via the Sanctum API (for dashboard-integrated apps) and will be expanded in v1 in future releases.
/api/v1/messages/send
Send a message through the specified channel. The message is queued and dispatched asynchronously — the API returns a message ID immediately.
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
| channel | string | Messaging channel to use. Currently: whatsapp. See Channels Overview for the full list. | |
| to | string | Recipient phone number (E.164: +15551234567). Auto-normalised — spaces and dashes stripped. | |
| message | string | Text body. Required when type is text (default). Omit for media-only messages. | |
| type | string | Message type: text (default), image, video, audio, document. | |
| session_id | uuid | WhatsApp session UUID. Optional only if the API key is already bound to a session. | |
| media_url | string | Publicly accessible URL of the media file. Required when type ≠text. |
أرسل رسالة نصية
curl -X POST https://zagely.com/api/v1/messages/send \
-H "X-Public-Key: pk_your_public_key" \
-H "X-Secret-Key: sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"session_id": "11111111-2222-3333-4444-555555555555",
"to": "+15551234567",
"message": "Hello from Zagely! 👋"
}'
أرسل صورة
curl -X POST https://zagely.com/api/v1/messages/send \
-H "X-Public-Key: pk_your_public_key" \
-H "X-Secret-Key: sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"session_id": "11111111-2222-3333-4444-555555555555",
"to": "+15551234567",
"type": "image",
"media_url": "https://example.com/promo.jpg",
"message": "Check out our latest offer!"
}'
استجابة 201
{
"status": "queued",
"message_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
/api/v1/messages
Returns a paginated list of messages (50 per page). All query parameters are optional.
| معامل الاستعلام | الوصف |
|---|---|
| channel | Filter by channel: whatsapp, telegram, messenger, etc. |
| session_id | Filter by session UUID. |
| contact_id | Filter by contact UUID. |
| direction | inbound or outbound. |
// 200 Response — paginated list
{
"current_page": 1,
"data": [
{
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"channel": "whatsapp",
"direction": "inbound",
"type": "text",
"content": "Hi, I need help!",
"status": "received",
"created_at": "2026-04-21T10:00:00Z",
"contact": { "id": "...", "name": "Jane Doe", "phone_number": "+15551234567" },
"session": { "id": "...", "name": "My Store Session" }
}
],
"per_page": 50,
"total": 1204
}
Every channel requires a connected "session" before messages can be sent. The concept differs per channel:
| القناة | Connection type | Managed via |
|---|---|---|
| WhatsApp Session (QR scan) | API (below) or Dashboard | |
| telegram | Telegram Bot (bot token) | Dashboard → Channels → Telegram |
| messenger | Messenger Page (Meta OAuth) | Dashboard → Channels → Messenger |
| viber | Viber Bot (auth token) | Dashboard → Channels → Viber |
| discord | Discord Bot (bot token) | Dashboard → Channels → Discord |
| Instagram Account (Meta OAuth) | Dashboard → Channels → Instagram | |
| webpush | Push Site Key (VAPID) | Dashboard → Channels → Web Push |
| Tenant SMTP config | Dashboard → Settings → Email |
WhatsApp Session API
/api/v1/sessions
Returns all WhatsApp sessions for your workspace with their current status.
// 200 Response
{
"current_page": 1,
"data": [
{
"id": "11111111-2222-3333-4444-555555555555",
"name": "Customer Support",
"phone_number": "+15551234567",
"status": "connected",
"daily_limit": 500,
"warmup_enabled": true,
"created_at": "2026-01-10T08:00:00Z"
}
],
"total": 3
}
/api/v1/sessions/{session}
Fetches the live status of a single session. Possible statuses: connecting, connected, disconnected, banned.
/api/whatsapp/sessions
Creates a new WhatsApp session and starts the QR-code connection flow.
| الحقل | مطلوب | الوصف |
|---|---|---|
| name | Human-readable label for the session (max 255). | |
| phone_number | Expected phone number (informational; set automatically on QR scan). |
// 201 Response
{
"id": "11111111-2222-3333-4444-555555555555",
"name": "Customer Support",
"status": "connecting",
"qr_code": "data:image/png;base64,iVBOR..."
}
/api/whatsapp/sessions/{session}
Updates session settings. All fields optional — only sent fields change.
| الحقل | النوع | الوصف |
|---|---|---|
| name | string | Human-readable label. |
| description | string | Optional description. |
| daily_limit | integer | Max messages per day (min 1). |
| warmup_enabled | boolean | Enable gradual send ramp-up. |
| warmup_start_limit | integer | Messages per day at warmup start. |
| warmup_max_limit | integer | Messages per day at warmup peak. |
| warmup_days | integer | Days to reach peak limit. |
| simulate_typing | boolean | Add realistic typing delay before each message. |
| الطريقة | المسار | الوصف |
|---|---|---|
| /api/whatsapp/sessions/{session}/qr | Current QR code image (base64). Poll while status = connecting. | |
| /api/whatsapp/sessions/{session}/disconnect | Gracefully disconnect. Status → disconnected. | |
| /api/whatsapp/sessions/{session} | Disconnect and permanently delete the session. |
/api/v1/contacts
Paginated, filterable list of contacts. Default page size is 50.
| معامل الاستعلام | الوصف |
|---|---|
| search | Partial match on name, phone_number, or email. |
| tags | Array — contacts that have ALL tags. e.g. ?tags[]=vip&tags[]=active. |
| is_blocked | boolean — true returns only blocked contacts. |
| opted_out | boolean — true returns opted-out contacts. |
| group_id | UUID — contacts belonging to this contact group. |
| sort_by | name, phone_number, email, created_at (default), updated_at. |
| sort_order | asc or desc (default). |
| per_page | Items per page 1–200. Default 50. |
// 200 Response
{
"current_page": 1,
"data": [
{
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"name": "Jane Doe",
"phone_number": "+15551234567",
"email": "jane@example.com",
"tags": ["vip", "active"],
"is_blocked": false,
"opted_out_at": null,
"custom_fields": { "city": "New York" },
"groups": [{ "id": "...", "name": "VIP Customers" }],
"created_at": "2026-01-15T09:00:00Z"
}
],
"total": 842,
"per_page": 50
}
/api/v1/contacts
Creates a new contact. Returns 422 if a contact with that phone number already exists in your workspace.
| الحقل | مطلوب | الوصف |
|---|---|---|
| phone_number | E.164 recommended (+15551234567). Non-digit chars stripped automatically. | |
| name | Display name. Defaults to phone number if omitted. | |
| Valid email address. | ||
| tags | String array: ["vip", "newsletter"]. | |
| notes | Free-text notes. | |
| custom_fields | Key-value object: {"city": "NY", "plan": "gold"}. |
curl -X POST https://zagely.com/api/v1/contacts \
-H "X-Public-Key: pk_your_public_key" \
-H "X-Secret-Key: sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+15551234567",
"name": "Jane Doe",
"email": "jane@example.com",
"tags": ["vip", "newsletter"],
"custom_fields": { "city": "New York", "plan": "gold" }
}'
| الطريقة | المسار | الوصف |
|---|---|---|
| /api/v1/contacts/{contact} | Get a contact with last 10 messages and all groups. | |
| /api/v1/contacts/{contact} | Update fields. All fields optional — only sent fields change. | |
| /api/v1/contacts/{contact} | Permanently delete contact and linked data. | |
| /api/contacts/{contact}/block | Block — contact stops receiving campaign messages. | |
| /api/contacts/{contact}/unblock | Unblock a previously blocked contact. | |
| /api/contacts/import | Bulk import from CSV/XLSX file (async). Returns 202. | |
| /api/contacts/export | Start async export with optional filters. Returns 202. |
Static groups are managed manually. Dynamic groups auto-update based on filters.
/api/v1/contact-groups
| الحقل | مطلوب | الوصف |
|---|---|---|
| name | Group label (max 255). | |
| type | static — manually managed; dynamic — auto-filtered. | |
| description | Optional description. | |
| filters | Required when type is dynamic. Keys: tags (array), custom_fields (object), created_from, created_to. |
// Static group
{ "name": "VIP Customers", "type": "static" }
// Dynamic group
{
"name": "New VIPs",
"type": "dynamic",
"filters": { "tags": ["vip"], "created_from": "2026-01-01" }
}
curl -X POST https://zagely.com/api/v1/contact-groups \
-H "X-Public-Key: pk_your_public_key" \
-H "X-Secret-Key: sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{ "name": "VIP Customers", "type": "static" }'
# Add contacts to a static group — by UUID or phone number
curl -X POST https://zagely.com/api/v1/contact-groups/{group_id}/contacts \
-H "X-Public-Key: pk_your_public_key" \
-H "X-Secret-Key: sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{ "phone_numbers": ["+201140220996", "+201001234567"] }'
| الطريقة | المسار | الوصف |
|---|---|---|
| /api/v1/contact-groups | List groups with contacts_count. | |
| /api/v1/contact-groups/{group} | Show group and first 100 contacts. | |
| /api/v1/contact-groups/{group} | Update name, description, or filters. | |
| /api/v1/contact-groups/{group} | Delete group (contacts are not deleted). | |
| /api/v1/contact-groups/{group}/contacts | Add members to a static group. Body: contact_ids (UUIDs) and/or phone_numbers (strings). | |
| /api/v1/contact-groups/{group}/contacts | Remove members from a static group. Body: contact_ids (UUIDs) and/or phone_numbers (strings). |
Reusable message templates with variable substitution. Use {{name}},
{{phone}},
or any custom field key.
/api/templates
| الحقل | مطلوب | الوصف |
|---|---|---|
| name | Unique template name within your workspace. | |
| channel | whatsapp, telegram, messenger, viber, webpush, email, discord, or all. | |
| content | Message body. Use {{variable}} placeholders for dynamic values. | |
| media_url | Attach media to the template. | |
| buttons | Array of quick-reply or CTA button objects. | |
| variables | Array of variable names. Auto-extracted from {{...}} in content if omitted. |
{
"name": "Order Confirmation",
"channel": "whatsapp",
"content": "Hi {{ name }}! Your order #{{ order_id }} is confirmed.
We ship by {{ ship_date }}. 🎉"
}
استجابة 201
{
"id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"name": "Order Confirmation",
"channel": "whatsapp",
"variables": ["name", "order_id", "ship_date"],
"created_at": "2026-04-21T12:00:00Z"
}
/api/templates/{template}/preview
Render the template with real values. Pass a contact_id (auto-fills contact fields) or a variables object.
// By contact
{ "contact_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" }
// By values
{ "variables": { "name": "Jane", "order_id": "ORD-001", "ship_date": "April 25" } }
/api/v1/campaigns
Creates a campaign in draft status. Start it separately via the /start endpoint. Campaigns cannot be edited once running.
| الحقل | مطلوب | الوصف |
|---|---|---|
| name | Campaign name (max 255). | |
| channel | Sending channel. Default: whatsapp. Values: whatsapp, telegram, messenger, viber, webpush, email, discord. | |
| session_id | UUID of the channel session/bot to use. Required for all channels except email. | |
| message_content | Message body sent to each recipient. | |
| target_type | all — every contact; group — a contact group; filter — custom filters. | |
| media_url | Attach a media file to the message. | |
| buttons | Array of quick-reply button objects. | |
| target_filters | Required when target_type is group or filter. Object with group_id, tags, etc. | |
| scheduled_at | ISO 8601 datetime in the future. Campaign launches automatically at this time. |
curl -X POST https://zagely.com/api/v1/campaigns \
-H "X-Public-Key: pk_your_public_key" \
-H "X-Secret-Key: sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"name": "April Flash Sale",
"channel": "whatsapp",
"session_id": "11111111-2222-3333-4444-555555555555",
"message_content": "Hi {{ name }}! Flash sale — 30% off today only. Shop
now 👉 https://example.com/sale",
"target_type": "group",
"target_filters": { "group_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" },
"scheduled_at": "2026-04-25T09:00:00Z"
}'
/api/campaigns/{campaign}/stats
{
"total_recipients": 1500,
"sent_count": 1450,
"delivered_count": 1380,
"read_count": 920,
"failed_count": 50,
"pending_count": 0,
"delivery_rate": 95.17,
"read_rate": 66.67
}
Lifecycle endpoints
| /api/v1/campaigns/{campaign}/start | draft, scheduled, paused → running | |
| /api/campaigns/{campaign}/pause | running → paused | |
| /api/campaigns/{campaign}/resume | paused → running | |
| /api/campaigns/{campaign}/cancel | scheduled, running, paused → canceled | |
| /api/campaigns/{campaign} | Update (draft/scheduled only) | |
| /api/campaigns/{campaign} | Delete (draft/scheduled/canceled only) |
Auto-replies triggered when a condition is met. Trigger types: keyword, welcome, away, fallback.
/api/automations
| الحقل | مطلوب | الوصف |
|---|---|---|
| session_id | WhatsApp session (or bot) this automation listens on. | |
| name | Automation label (max 255). | |
| channel | Channel scope. Default: whatsapp. Values: whatsapp, telegram, messenger, viber, webpush, email, discord, all. | |
| trigger_type | keyword, welcome (first contact), away (outside hours), fallback (no match). | |
| trigger_value | Keyword to match. Required when trigger_type is keyword. | |
| response_content | Message body to send as the automatic reply. | |
| response_media_url | Media attachment for the response. | |
| response_buttons | Array of quick-reply buttons. | |
| conditions | Extra conditions object. | |
| priority | Integer 0–100. Higher priority evaluated first. Default 0. |
{
"session_id": "11111111-2222-3333-4444-555555555555",
"name": "Price Enquiry Reply",
"channel": "whatsapp",
"trigger_type": "keyword",
"trigger_value": "price",
"response_content": "Hi {{ name }}! Our plans start at $29/month. Visit
https://example.com/pricing for details.",
"priority": 10
}
| الطريقة | المسار | الوصف |
|---|---|---|
| /api/automations | List automations. Filter: session_id, is_active, trigger_type. | |
| /api/automations/{automation} | Show automation with last 50 trigger logs. | |
| /api/automations/{automation} | Update any field. | |
| /api/automations/{automation} | Delete automation. | |
| /api/automations/{automation}/toggle | Toggle is_active on/off. |
Read-only endpoints. Date filters use YYYY-MM-DD format.
/api/analytics/overview
High-level snapshot: total contacts, active sessions, campaigns this month, messages today. No parameters.
{"total_contacts":4821,"active_sessions":3,"campaigns_this_month":12,"messages_today":347}
/api/analytics/campaigns
Campaign delivery summary. Optional: from_date, to_date.
{"summary":{"total_campaigns":12,"total_sent":14800,"avg_delivery_rate":95.94},"campaigns":[...]}
/api/analytics/sessions
Session message stats. Optional: from_date, to_date.
{"summary":{"total_sessions":3,"active_sessions":2,"total_messages_sent":8430},"sessions":[...]}
/api/analytics/contacts
Contact growth data. Optional: from_date, to_date (defaults to last 30 days).
{"stats":{"total_contacts":4821,"new_contacts":342},"growth_data":[{"date":"2026-04-21","count":15}]}
/api/analytics/message-activity
Hourly message volume by direction. Optional: from_date, to_date (defaults to last 7 days).
[{"date":"2026-04-21","hour":9,"direction":"outbound","count":124}]
Webhooks push real-time event notifications to your server as HTTP POST requests. No polling required.
| الطريقة | نقطة النهاية | Purpose |
|---|---|---|
| /api/v1/webhooks | List all webhooks for the workspace. | |
| /api/v1/webhooks | Register a new webhook. Secret auto-generated if omitted. | |
| /api/v1/webhooks/{id} | Retrieve a single webhook. | |
| /api/v1/webhooks/{id} | Update URL, events list, or active state. | |
| /api/v1/webhooks/{id} | Remove the webhook. | |
| /api/v1/webhooks/{id}/deliveries | Paginated delivery log (status, signature, attempts, duration). | |
| /api/v1/webhooks/{id}/test | Dispatch a synthetic webhook.test event to the configured URL. |
| Header | القيمة |
|---|---|
| X-Zagely-Event | Event name (e.g. message.received). |
| X-Zagely-Delivery | Unique delivery UUID. Use it for idempotency on your side. |
| X-Zagely-Timestamp | Unix seconds when the request was signed. |
| X-Zagely-Signature | sha256=<hex> — HMAC-SHA256 of "{timestamp}.{body}". |
| Content-Type | application/json. |
The signed string is
{timestamp}.{raw_body}.
Always use a timing-safe comparison.
$timestamp = $_SERVER['HTTP_X_ZAGELY_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_ZAGELY_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body,
'your_webhook_secret');
if (! hash_equals($expected, $signature)) {
http_response_code(401); exit;
}
$event = json_decode($body, true);
// Node.js / Express — use express.raw() to keep the exact body bytes
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.headers['x-zagely-timestamp'];
const signature = req.headers['x-zagely-signature'];
const expected = 'sha256=' + require('crypto')
.createHmac('sha256', process.env.ZAGELY_WEBHOOK_SECRET)
.update(timestamp + '.' + req.body.toString('utf8'))
.digest('hex');
if (!require('crypto').timingSafeEqual(Buffer.from(signature),
Buffer.from(expected))) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
res.sendStatus(200);
});
| الحدث | متى يتم التفعيل |
|---|---|
| message.received | New inbound message on any connected channel. |
| message.sent | Outbound message successfully dispatched. |
| message.delivered | Channel confirms delivery to recipient device. |
| message.read | Recipient reads the message (WhatsApp & Email). |
| message.failed | Delivery failed after all retries. |
| campaign.started | Campaign begins processing recipients. |
| campaign.completed | All recipients have been processed. |
| campaign.paused | Running campaign is paused. |
| contact.created | New contact record is created. |
| contact.updated | Contact fields are updated. |
| contact.opted_out | Contact unsubscribes from email or push. |
| session.connected | Session connects successfully. |
| session.disconnected | Session loses connection. |
| webhook.test | Synthetic event triggered from the dashboard or the /test endpoint. |
{
"event": "message.received",
"tenant_id": "uuid-of-tenant",
"delivery_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"occurred_at": "2026-04-21T10:30:00Z",
"data": {
"contact": { "id": "...", "name": "Jane Doe", "phone_number": "+15551234567", "email":
null },
"channel": "whatsapp",
"content": "Hello, I need help with my order."
}
}
Retry Policy: Any response other than 2xx (or a timeout after 10 s) triggers a retry. Back-off schedule: 10 s, 60 s, 5 min, 30 min, 2 h. After 5 failed attempts the delivery is marked failed and surfaced in Webhook Logs for manual replay.
الكل /api/v1/*
responses use the same JSON envelope so that clients can handle errors generically.
{
"error": {
"code": "machine_readable_snake_case",
"message": "Human-readable description.",
"details": { /* optional context */ }
}
}
| الحالة | المعنى | Common cause |
|---|---|---|
| 200 OK | Success. | Standard success for GET and some POST. |
| 201 Created | Resource created. | Successful POST that creates a resource. |
| 202 Accepted | Job queued. | Async operations: import, export, send. |
| 400 Bad Request | Malformed request. | Invalid JSON or malformed Idempotency-Key. |
| 401 Unauthorized | Auth failure. | Missing, wrong, inactive, or expired API keys. |
| 403 Forbidden | Insufficient permissions. | API key lacks the required ability. |
| 404 Not Found | Resource not found. | Wrong UUID or belongs to another workspace. |
| 422 Unprocessable | Validation failed. | Missing / invalid field, duplicate unique value. |
| 429 Too Many Requests | Rate limit or quota exceeded. | Back off. Inspect Retry-After. |
| 500 Internal Error | Server error. | Contact support with the full response body. |
| 503 Service Unavailable | Dependency down. | Database or upstream temporarily unreachable. |
| error.code | الحالة | الوصف |
|---|---|---|
| credentials_missing | 401 | No X-Public-Key / X-Secret-Key header pair was sent. |
| invalid_credentials | 401 | The public key does not match or the secret is wrong. |
| key_inactive | 401 | The key is disabled or past its expires_at. |
| unauthenticated | 401 | Session authentication failed (non key-auth endpoints). |
| insufficient_ability | 403 | Key lacks the required scope. details.required_ability contains the missing scope. |
| forbidden | 403 | Authorization policy denied the request. |
| not_found | 404 | Resource does not exist or belongs to another workspace. |
| validation_failed | 422 | One or more fields failed validation. details.errors is the errors object. |
| invalid_idempotency_key | 400 | The Idempotency-Key header violates length or character rules. |
| rate_limit_exceeded | 429 | Per-minute burst limit hit. |
| daily_quota_exceeded | 429 | Today's request quota is exhausted. |
| monthly_quota_exceeded | 429 | This month's request quota is exhausted. |
// 422 — validation_failed
{
"error": {
"code": "validation_failed",
"message": "The given data was invalid.",
"details": { "errors": { "channel": ["The channel field is required."] } }
}
}
// 403 — insufficient_ability
{
"error": {
"code": "insufficient_ability",
"message": "The API key does not have the required ability.",
"details": { "required_ability": "messages.send" }
}
}
// 429 — daily_quota_exceeded
{
"error": {
"code": "daily_quota_exceeded",
"message": "Daily request quota exceeded.",
"details": { "limit": 10000 }
}
}
Ready to integrate?
Generate your API key pair and start building today.