Slide API
Integrate email campaigns, Instagram messaging, WhatsApp campaigns, SMS, RCS, OTP verification, Shopify commerce, AI voice calling, and real-time WebSocket events into your own applications using the Slide REST API.
Introduction
The Slide API is a RESTful API over HTTPS. Every response body is JSON. Requests that include a body must set Content-Type: application/json.
| Property | Value |
|---|---|
| Base URL | https://slide.synquic.com/api/v1 |
| Protocol | HTTPS only |
| Format | JSON (application/json) |
| API Version | v1 |
| Auth scheme | Bearer token (API key) |
2026-03-24T10:00:00.000Z). Pagination follows a consistent { data, meta } envelope./messages/send endpoint, or jump straight to the technical reference below.SDKs & Code Examples
Official client libraries for TypeScript/Node.js and Python. Both are fully typed, zero-configuration, and wrap every endpoint in a resource-based API. The latest releases add sms, rcs, and otp resources, the unified messages resource for the One API endpoint, plus WhatsApp conversation and campaign methods: client.messages.send(...), client.sms.send(...), client.otp.send(...), client.rcs.send(...), and more. If you prefer raw HTTP, jump to the Without an SDK section below.
TypeScript SDK with full type inference, ESM + CJS dual build, zero runtime dependencies. Works in Node.js 18+ and any modern runtime.
npm install @synquic/slide # or: pnpm add @synquic/slide # or: yarn add @synquic/slide
Python SDK with sync and async clients, full type hints via TypedDict, context manager support. Requires Python 3.9+ and httpx.
pip install synquic-slide
TypeScript SDK
Install the package and instantiate a client with your API key. All methods are fully typed and return typed response objects.
import { SlideClient } from '@synquic/slide'; const slide = new SlideClient({ apiKey: process.env.SLIDE_API_KEY! }); // Send a transactional email const sent = await slide.email.send({ templateId: 'tmpl_welcome', recipient: { to: 'user@example.com', firstName: 'Rahul' }, fromName: 'Acme Store', fromEmail: 'orders@acme.com', variables: { orderId: '#1042', total: '₹2,499' }, }); console.log(sent.messageId, sent.status); // Send a WhatsApp template const msg = await slide.whatsapp.sendTemplate({ to: '+919876543210', templateName: 'order_shipped', languageCode: 'en', }); console.log(msg.wamid); // Initiate an outbound AI voice call const call = await slide.voice.initiateOutboundCall({ agentId: 'va_01JXYZ...', toNumber: '+919876543210', callContext: { customerName: 'Priya', purpose: 'appointment_confirmation' }, }); console.log(call.id, call.status); // vc_01J... INITIATED // List contacts with pagination const contacts = await slide.contacts.list({ page: 1, limit: 50 }); console.log(contacts.meta.total, contacts.data.length); // Shopify - validate discount code const discount = await slide.shopify.validateDiscount({ code: 'SAVE20' }); if (discount.valid) { console.log(`${discount.value}% off, expires ${discount.expiresAt}`); } // Instagram - get profile and send a DM const profile = await slide.instagram.getProfile(); await slide.instagram.sendMessage({ recipientIgsid: '123456789', message: 'Thanks for reaching out!', }); // SMS, RCS, and OTP await slide.sms.send({ senderId: 'ACMEIN', to: ['919876543210'], message: 'Your order has shipped!', }); const otp = await slide.otp.send({ widgetId: 'wgt_01J...', identifier: '+919876543210' }); await slide.otp.verify({ requestId: otp.requestId, otp: '123456' }); await slide.rcs.send({ to: '919876543210', templateId: 'rcst_01J...' }); // WhatsApp conversations and campaigns const history = await slide.whatsapp.getConversationByPhone({ phone: '919876543210', limit: 50 }); const campaigns = await slide.whatsapp.listCampaigns({ page: 1, limit: 20 });
Error Handling
import { SlideClient, SlideAuthError, SlideScopeError, SlideValidationError, SlideNotFoundError, SlideError, } from '@synquic/slide'; const slide = new SlideClient({ apiKey: process.env.SLIDE_API_KEY! }); try { await slide.email.send({ /* ... */ }); } catch (err) { if (err instanceof SlideAuthError) { console.error('Invalid API key - check SLIDE_API_KEY'); } else if (err instanceof SlideScopeError) { console.error('Missing scope:', err.message); // e.g. "email:send" } else if (err instanceof SlideValidationError) { console.error('Bad request:', err.body); } else if (err instanceof SlideNotFoundError) { console.error('Resource not found'); } else if (err instanceof SlideError) { console.error(`HTTP ${err.statusCode}:`, err.message); } }
Python SDK
The Python SDK ships two client classes: SlideClient (synchronous, backed by httpx.Client) and AsyncSlideClient (async, backed by httpx.AsyncClient). Both support context managers.
Sync Client
import os from synquic_slide import SlideClient slide = SlideClient(api_key=os.environ["SLIDE_API_KEY"]) # Send a transactional email slide.email.send( recipient={"to": "user@example.com", "firstName": "Rahul"}, template_id="tmpl_welcome", from_name="Acme Store", from_email="orders@acme.com", variables={"orderId": "#1042", "total": "₹2,499"}, ) # Send a WhatsApp template msg = slide.whatsapp.send_template( to="+919876543210", template_name="order_shipped", language_code="en", ) print(msg["wamid"]) # Initiate an outbound AI voice call call = slide.voice.initiate_outbound_call( agent_id="va_01JXYZ...", to_number="+919876543210", call_context={"customerName": "Priya", "purpose": "appointment_confirmation"}, ) print(call["id"], call["status"]) # vc_01J... INITIATED # List contacts contacts = slide.contacts.list(page=1, limit=50) print(contacts["meta"]["total"]) # Validate a Shopify discount code discount = slide.shopify.validate_discount(code="SAVE20") if discount["valid"]: print(f'{discount["value"]}% off, expires {discount["expiresAt"]}') # SMS, RCS, and OTP slide.sms.send(sender_id="ACMEIN", to=["919876543210"], message="Your order has shipped!") otp = slide.otp.send(widget_id="wgt_01J...", identifier="+919876543210") slide.otp.verify(request_id=otp["requestId"], otp="123456") slide.rcs.send(to="919876543210", template_id="rcst_01J...")
Async Client
import asyncio import os from synquic_slide import AsyncSlideClient async def main(): async with AsyncSlideClient(api_key=os.environ["SLIDE_API_KEY"]) as slide: # Fetch contacts and send a WhatsApp message concurrently contacts, agents = await asyncio.gather( slide.contacts.list(page=1, limit=20), slide.voice.list_agents(), ) print(f'{contacts["meta"]["total"]} contacts, {agents["meta"]["total"]} agents') call = await slide.voice.initiate_outbound_call( agent_id=agents["data"][0]["id"], to_number="+919876543210", call_context={"customerName": "Priya"}, ) print(call["id"], call["status"]) asyncio.run(main())
Error Handling
from synquic_slide import ( SlideClient, SlideAuthError, SlideScopeError, SlideNotFoundError, SlideValidationError, SlideError, ) slide = SlideClient(api_key="sk_live_...") try: slide.email.send(...) except SlideAuthError: print("Invalid API key - check SLIDE_API_KEY") except SlideScopeError as e: print(f"Missing scope: {e}") # e.g. "email:send" except SlideValidationError as e: print(f"Bad request: {e.body}") except SlideNotFoundError: print("Resource not found") except SlideError as e: print(f"HTTP {e.status_code}: {e}")
Without an SDK
The Slide API is a standard HTTP REST API - any HTTP client works.
curl
export SLIDE_API_KEY="sk_live_YOUR_API_KEY" export SLIDE_BASE="https://slide.synquic.com/api/v1" # Send an email using a template curl -X POST "$SLIDE_BASE/email/send" \ -H "Authorization: Bearer $SLIDE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "templateId": "YOUR_TEMPLATE_ID", "recipient": { "to": "hello@example.com", "firstName": "Alex" }, "variables": { "company": "Acme" }, "fromName": "Acme", "fromEmail": "hi@acme.com" }' # Initiate an outbound voice call curl -X POST "$SLIDE_BASE/voice/calls/outbound" \ -H "Authorization: Bearer $SLIDE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "va_01JXYZ...", "toNumber": "+919876543210" }'
Node.js fetch
const BASE = 'https://slide.synquic.com/api/v1'; const KEY = process.env.SLIDE_API_KEY!; const headers = { 'Authorization': `Bearer ${KEY}`, 'Content-Type': 'application/json', }; // Send email const emailRes = await fetch(`${BASE}/email/send`, { method: 'POST', headers, body: JSON.stringify({ templateId: 'YOUR_TEMPLATE_ID', recipient: { to: 'user@example.com', firstName: 'Alex' }, variables: { company: 'Acme Inc' }, fromName: 'Acme Inc', fromEmail: 'hi@acme.com', }), }); const email = await emailRes.json(); console.log(email); // { messageId, status, to, templateId, templateName } // Search Shopify products const prodRes = await fetch(`${BASE}/shopify/products/search?q=sneakers&limit=5`, { headers }); const products = await prodRes.json(); console.log(products.data); // [{ id, title, vendor, variants }, ...]
Authentication
Every API request must include a valid API key in the Authorization header as a Bearer token. Keys are prefixed with sk_live_ for production.
To create an API key, navigate to Dashboard → API Keys or visit slide.synquic.com/dashboard/api-keys.
# Include in every request curl https://slide.synquic.com/api/v1/email/send \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json"
Scopes
Each API key is issued with a specific set of scopes that determine what operations it can perform. Attempting an operation without the required scope returns 403 Forbidden.
| Scope | Description |
|---|---|
| contacts:read | List and search the unified contacts database |
| contacts:block | Block or unblock a contact across every channel |
| email:send | Send transactional or campaign emails |
| email:templates:read | List and retrieve email templates |
| instagram:messages:read | Read Instagram conversations and profile |
| instagram:messages:send | Send Instagram direct messages |
| instagram:insights:read | Read Instagram analytics and insights |
Rate Limits
Each API key has a configurable per-minute limit, rateLimitPerMinute. The default is 60 requests per minute, and it can be set anywhere from 1 to 10,000 per key in Admin Settings → API Keys (IP and rate settings).
Rate limit state is tracked on a sliding 60 second window, per key and per client IP. Two servers calling the API with the same key from different IPs each get their own window.
| Header | Description |
|---|---|
X-RateLimit-Limit | The configured requests-per-minute limit for this key |
X-RateLimit-Remaining | Requests remaining in the current 60 second window |
Retry-After | Seconds to wait before retrying (on 429 responses only) |
X-RateLimit-Limit and X-RateLimit-Remaining are returned on every request. When the limit is exceeded, the API returns HTTP 429 Too Many Requests with a Retry-After header:
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Try again in 23 seconds.",
"retryAfter": 23
}Recommended Client Behavior
On a 429 response, honor the Retry-After header and wait that many seconds before retrying. For repeated 429s, apply exponential backoff with jitter. Never retry in a tight loop.
async function requestWithBackoff(url: string, init: RequestInit, maxRetries = 5) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const res = await fetch(url, init); if (res.status !== 429) return res; // Honor Retry-After, fall back to exponential backoff with jitter const retryAfter = Number(res.headers.get('Retry-After')); const backoffMs = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : Math.min(60_000, 2 ** attempt * 1000) + Math.random() * 500; await new Promise((r) => setTimeout(r, backoffMs)); } throw new Error('Rate limited: retries exhausted'); }
POST /v1/otp/send and POST /v1/otp/retry) additionally throttle per identifier: each phone number is limited to a maximum number of sends per hour, configured on the OTP widget. This applies on top of the key's per-minute limit.IP Whitelisting & Key Security
Every API key can be locked down to a fixed set of source IP addresses. Enable ipWhitelistEnabled and set allowedIps for a key in Admin Settings → API Keys (IP and rate settings). When enabled, requests from any other IP are rejected with 403 Forbidden, and the offending IP is included in the error message so you can update the whitelist.
{
"statusCode": 403,
"error": "Forbidden",
"message": "Request from IP 203.0.113.42 is not allowed for this API key"
}x-forwarded-for header, so keys stay locked to the true origin IP.Key Security Model
| Feature | Description |
|---|---|
| Environments | Keys are issued as live (sk_live_) or test (sk_test_) and are scoped to that environment |
| Expiry | Keys support an optional expiresAt date, expired keys are rejected with 401 |
| One-time display | The plaintext key is shown exactly once at creation, only a SHA-256 hash is stored |
| Usage logs | Every request is logged per key: endpoint, HTTP status, source IP, and duration |
Best Practices
| Practice | Why |
|---|---|
| Grant least-privilege scopes | A leaked key with only sms:logs:read cannot send messages |
| Rotate keys periodically | Create a new key, migrate traffic, then revoke the old one |
| Use a separate key per integration | Per-key usage logs make it easy to trace and revoke a single consumer |
| Enable IP whitelisting on server keys | Even a leaked key is useless outside your infrastructure |
Error Codes
All errors follow the same JSON envelope. The message field is human-readable and safe to display to end users.
{
"statusCode": 400,
"error": "Bad Request",
"message": "recipient.to must be a valid email address"
}| HTTP Code | Meaning | Common Cause |
|---|---|---|
| 200 OK | Request succeeded | Standard successful response |
| 201 Created | Resource created | Contact or resource was created |
| 400 Bad Request | Invalid input | Missing required field, bad format |
| 401 Unauthorized | Missing or invalid API key | No Authorization header |
| 403 Forbidden | Insufficient scope | Key does not have required scope |
| 404 Not Found | Resource not found | Invalid ID or path |
| 409 Conflict | Duplicate resource | Contact with email already exists |
| 422 Unprocessable | Validation failed | Field present but value invalid |
| 429 Too Many Requests | Rate limit exceeded | Exceeded the key's configured per-minute limit |
| 500 Internal Error | Server error | Unexpected error contact support |
Contacts API
Read your organization's unified contact database. Contacts are shared across all channels Instagram, email, and future integrations.
List Contacts
/v1/contactsList contacts with pagination and optional fuzzy search across name, username, email, and phone.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (1-indexed) |
limit | number | 50 | Results per page (max 100) |
search | string | Optional fuzzy search by name, username, email, or phone |
curl "https://slide.synquic.com/api/v1/contacts?page=1&limit=50&search=john" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const params = new URLSearchParams({ page: '1', limit: '50', search: 'john' }); const res = await fetch(`https://slide.synquic.com/api/v1/contacts?${params}`, { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' }, }); const { data, meta } = await res.json();
Response
{
"data": [
{
"id": "cnt_abc123",
"username": "john_doe",
"name": "John Doe",
"email": "john@example.com",
"phone": "+91987654321",
"profilePic": "https://...",
"source": "instagram",
"tags": [],
"createdAt": "2026-01-15T10:30:00Z"
}
],
"meta": {
"total": 312,
"page": 1,
"limit": 50,
"totalPages": 7
}
}Block a Contact
/v1/contacts/{id}/blockBlock a contact across every channel. Requires the contacts:block scope, not contacts:read.
409 CONTACT_BLOCKED. Message history is preserved and unblocking restores everything.Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
reason | string | No | Up to 500 chars. Shown in the dashboard and returned in the 409 body of every refused send. |
curl -X POST "https://slide.synquic.com/api/v1/contacts/cnt_abc123/block" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"reason": "Spam"}'
Response
{
"success": true,
"contactId": "cnt_abc123",
"isBlocked": true
}Idempotent, blocking an already-blocked contact returns 200, never an error, so retries and duplicated webhook deliveries are safe. The id accepts either the unifiedContactId or the id returned by GET /v1/contacts, both resolve to the same contact.
GET /v1/whatsapp/conversations/by-phone returns 403 with code CONTACT_BLOCKED, not a 404, so your integration can tell "blocked" apart from "no such conversation".Unblock a Contact
/v1/contacts/{id}/unblockRestore a blocked contact. Conversations reappear in every inbox with their full history.
curl -X POST "https://slide.synquic.com/api/v1/contacts/cnt_abc123/unblock" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Messages API
The Messages API is a single, unified endpoint for sending on any channel, WhatsApp, SMS, RCS, Email, or Instagram, through one request contract. Pick a primary channel, optionally add an ordered fallbackChannels chain for phone-based channels, and the API takes care of routing and delivery. This is purely additive: every existing per-channel endpoint documented below (/whatsapp/send-template, /sms/send, /rcs/send, /email/send, /instagram/messages) continues to work exactly as before, unchanged. Use whichever fits your integration: the unified endpoint for cross-channel routing and fallback, or a dedicated endpoint for channel-specific control.
<channel>:send scope for whichever channel(s) you specify in channel and fallbackChannels, for example whatsapp:send together with sms:send for a WhatsApp request with an SMS fallback.How It Works
- 1Every request lists a primary
channeland, optionally, an orderedfallbackChannelschain. - 2Before anything is attempted, the API validates that your key holds the
sendscope for every channel in the chain, and that the required fields for each are present, so you get a fast, cheap 400/403 instead of a partial send. - 3The primary channel is attempted first through the exact same underlying send logic as its dedicated endpoint (same billing, same delivery logs).
- 4If it fails, the API moves to the next channel in
fallbackChannels, in order, until one succeeds or the chain is exhausted. - 5The response always includes an
attemptsarray, so you can see exactly which channel(s) were tried and why any of them failed, not just the final outcome.
Send a Message (Any Channel)
/v1/messages/sendSend a message on any supported channel through one request contract, with an optional ordered fallback chain across phone-based channels.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient identifier: E.164 phone number (whatsapp/sms/rcs), email address (email), or Instagram IGSID (instagram) |
channel | string | Yes | Primary channel: whatsapp, sms, rcs, email, or instagram |
fallbackChannels | string[] | No | Ordered fallback chain, tried in order if the primary channel fails. Phone-based channels only (whatsapp, sms, rcs); cannot be combined with email or instagram |
message | string | Conditional | Free-text body. Required for instagram, and required for sms (sms always needs a message body) |
template.name | string | Conditional | WhatsApp template name. Required when channel or a fallback channel is whatsapp |
template.id | string | Conditional | RCS or Email template ID. Required for rcs, required for email |
template.languageCode | string | Conditional | WhatsApp language code, e.g. "en". Required for whatsapp |
template.variables | object | No | Template merge variables (maps to RCS rcsVariables / Email merge variables) |
template.components | array | No | Advanced: raw WhatsApp template components array |
sms.senderId | string | Conditional | Required when channel or a fallback channel is sms |
sms.route | string | No | SMS route override |
email.subject | string | No | Override the template subject |
email.fromName | string | Conditional | Sender display name. Required when channel is email |
email.fromEmail | string | Conditional | Sender email address. Required when channel is email |
email.replyTo | string | No | Reply-to email address |
email.firstName | string | No | Maps to {{first_name}} variable |
email.lastName | string | No | Maps to {{last_name}} variable |
Example 1: Simple WhatsApp Send
curl -X POST https://slide.synquic.com/api/v1/messages/send \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+919876543210", "channel": "whatsapp", "template": { "name": "order_confirmation", "languageCode": "en", "variables": { "1": "John", "2": "ORD-12345" } } }'
const response = await fetch('https://slide.synquic.com/api/v1/messages/send', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ to: '+919876543210', channel: 'whatsapp', template: { name: 'order_confirmation', languageCode: 'en', variables: { '1': 'John', '2': 'ORD-12345' }, }, }), }); const data = await response.json(); // { status: "sent", channelUsed: "whatsapp", messageId: "wamid.HBg...", attempts: [...] }
Example 2: WhatsApp with SMS Fallback
curl -X POST https://slide.synquic.com/api/v1/messages/send \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+919876543210", "channel": "whatsapp", "fallbackChannels": ["sms"], "message": "Your order ORD-12345 has shipped!", "template": { "name": "order_shipped", "languageCode": "en" }, "sms": { "senderId": "ACMEIN" } }'
import requests response = requests.post( 'https://slide.synquic.com/api/v1/messages/send', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'to': '+919876543210', 'channel': 'whatsapp', 'fallbackChannels': ['sms'], 'message': 'Your order ORD-12345 has shipped!', 'template': { 'name': 'order_shipped', 'languageCode': 'en', }, 'sms': { 'senderId': 'ACMEIN' }, } ) data = response.json() # {'status': 'sent', 'channelUsed': 'whatsapp', 'messageId': 'wamid.HBg...', 'attempts': [...]}
Response
{
"status": "sent",
"channelUsed": "whatsapp",
"messageId": "wamid.HBg...",
"attempts": [
{ "channel": "whatsapp", "status": "sent", "messageId": "wamid.HBg..." }
]
}Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | Missing required field(s) | Lists exactly which field(s) are missing for which channel, e.g. template.id, email.fromEmail |
| 403 | Insufficient scope | The API key is missing the <channel>:send scope required for the requested channel(s) |
| 502 | All channels failed to deliver the message | The primary channel and every fallback channel in the chain failed; see the attempts array for the per-channel error |
// 400: missing required field(s)
{
"message": "Missing required field(s) for channel \"email\": template.id, email.fromEmail"
}
// 403: insufficient scope
{
"message": "Insufficient scope. Required: whatsapp:send"
}
// 502: all channels failed
{
"message": "All channels failed to deliver the message",
"attempts": [
{ "channel": "whatsapp", "status": "failed", "error": "..." },
{ "channel": "sms", "status": "failed", "error": "..." }
]
}Email API
The Email API lets you send transactional emails and manage templates programmatically. All email operations are scoped per organization.
Send Email
/v1/email/sendSend a transactional email using a pre-built template. Raw HTML is not accepted create templates in the dashboard and reference them by template ID. Each send deducts 1 email credit from your wallet.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string | Yes | Template ID (get from GET /email/templates or the dashboard Templates page) |
recipient.to | string | Yes | Recipient email address |
recipient.firstName | string | No | Maps to {{first_name}} variable |
recipient.lastName | string | No | Maps to {{last_name}} variable |
variables | object | No | Key-value pairs for template variables, e.g. {"company": "Acme"} |
subject | string | No | Override the template subject (uses template subject if omitted) |
fromName | string | Yes | Sender display name |
fromEmail | string | Yes | Sender email (must match verified SMTP or domain) |
replyTo | string | No | Reply-to email address |
Important: Raw HTML is not supported in the API. Create your email templates in the dashboard (Email > Templates) using the visual editor or HTML code editor, then use the template ID here. This ensures all emails are tracked, branded, and include required unsubscribe links. Each send deducts 1 email credit from your wallet balance.
curl -X POST https://slide.synquic.com/api/v1/email/send \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "recipient": { "to": "user@example.com", "firstName": "John" }, "variables": { "company": "Acme Corp", "order_id": "ORD-12345" }, "fromName": "Your Company", "fromEmail": "hello@yourdomain.com" }'
const response = await fetch('https://slide.synquic.com/api/v1/email/send', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ templateId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', recipient: { to: 'user@example.com', firstName: 'John', }, variables: { company: 'Acme Corp', order_id: 'ORD-12345', }, fromName: 'Your Company', fromEmail: 'hello@yourdomain.com', }), }); const data = await response.json(); // { messageId: "msg_01J...", status: "sent", to: "user@example.com", // templateId: "a1b2c3d4...", templateName: "Order Confirmation" }
import requests response = requests.post( 'https://slide.synquic.com/api/v1/email/send', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'templateId': 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'recipient': { 'to': 'user@example.com', 'firstName': 'John', }, 'variables': { 'company': 'Acme Corp', 'order_id': 'ORD-12345', }, 'fromName': 'Your Company', 'fromEmail': 'hello@yourdomain.com', } ) data = response.json() # {'messageId': 'msg_01J...', 'status': 'sent', 'to': 'user@example.com', # 'templateId': 'a1b2c3d4...', 'templateName': 'Order Confirmation'}
Response
{
"messageId": "msg_01JXYZ...",
"status": "sent",
"to": "user@example.com",
"templateId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"templateName": "Order Confirmation"
}Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | Template not found | The templateId does not exist or does not belong to your organization |
| 400 | Insufficient email credits | Your wallet has no email credits remaining. Purchase more in Settings > Wallet |
| 400 | No verified SMTP connection | Set up and verify an SMTP connection first in Email > SMTP |
| 400 | Template has no HTML content | The template is empty. Edit it in the dashboard first |
List Email Templates
/v1/email/templatesRetrieve all email templates for the organization.
curl https://slide.synquic.com/api/v1/email/templates \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const res = await fetch('https://slide.synquic.com/api/v1/email/templates', { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' }, }); const { data, meta } = await res.json();
Response
{
"data": [
{
"id": "tpl_01JXYZ...",
"name": "Welcome Email",
"subject": "Welcome to {{company_name}}",
"createdAt": "2026-01-15T09:00:00.000Z",
"updatedAt": "2026-02-10T12:30:00.000Z"
}
],
"meta": {
"total": 1,
"page": 1,
"limit": 20,
"totalPages": 1
}
}Get Email Template
/v1/email/templates/:idRetrieve a single email template by its ID, including the full HTML body.
curl https://slide.synquic.com/api/v1/email/templates/tpl_01JXYZ \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const res = await fetch('https://slide.synquic.com/api/v1/email/templates/tpl_01JXYZ', { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' }, }); const template = await res.json();
Response
{
"id": "tpl_01JXYZ...",
"name": "Welcome Email",
"subject": "Welcome to {{company_name}}",
"html": "<h1>Welcome!</h1><p>Thanks for joining {{company_name}}.</p>",
"text": "Welcome! Thanks for joining {{company_name}}.",
"createdAt": "2026-01-15T09:00:00.000Z",
"updatedAt": "2026-02-10T12:30:00.000Z"
}List Email Contacts
/v1/email/contactsList the organization's email contacts with pagination and optional search.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (1-indexed) |
limit | number | 50 | Results per page (max 100) |
search | string | Optional search by name or email |
curl "https://slide.synquic.com/api/v1/email/contacts?page=1&limit=50" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "ec_01JXYZ...",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"subscribed": true,
"createdAt": "2026-02-01T09:00:00.000Z"
}
],
"meta": { "total": 312, "page": 1, "limit": 50, "totalPages": 7 }
}Create Email Contact
/v1/email/contactsCreate a new email contact. Returns 409 Conflict if a contact with the same email already exists.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Contact email address |
firstName | string | No | First name |
lastName | string | No | Last name |
curl -X POST https://slide.synquic.com/api/v1/email/contacts \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "john@example.com", "firstName": "John", "lastName": "Doe" }'
Response
{
"id": "ec_01JXYZ...",
"email": "john@example.com",
"firstName": "John",
"lastName": "Doe",
"subscribed": true,
"createdAt": "2026-04-08T12:30:00.000Z"
}Instagram API
The Instagram API provides access to your connected Instagram Business account including profile data, conversations, message sending, and analytics. Requires an active Instagram integration in your organization.
404 Not Found.Get Instagram Profile
/v1/instagram/profileRetrieve your connected Instagram Business account profile information.
curl https://slide.synquic.com/api/v1/instagram/profile \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const res = await fetch('https://slide.synquic.com/api/v1/instagram/profile', { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' }, }); const profile = await res.json();
Response
{
"id": "17841400455970661",
"username": "yourbusiness",
"name": "Your Business Name",
"biography": "Official business account",
"followersCount": 12450,
"followsCount": 310,
"mediaCount": 89,
"profilePictureUrl": "https://example.com/profile.jpg",
"website": "https://yourbusiness.com"
}List Conversations
/v1/instagram/conversationsRetrieve a paginated list of Instagram DM conversations.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (1-indexed) |
limit | number | 20 | Conversations per page (max 50) |
curl "https://slide.synquic.com/api/v1/instagram/conversations?page=1&limit=20" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
import requests res = requests.get( 'https://slide.synquic.com/api/v1/instagram/conversations', headers={'Authorization': 'Bearer sk_live_YOUR_API_KEY'}, params={'page': 1, 'limit': 20} ) data = res.json()
Response
{
"data": [
{
"id": "conv_01JXYZ...",
"senderId": "112233445566778",
"senderUsername": "johndoe",
"senderName": "John Doe",
"lastMessage": "Thanks for the info!",
"lastMessageAt": "2026-03-24T08:15:00.000Z",
"unreadCount": 2
}
],
"meta": {
"total": 87,
"page": 1,
"limit": 20,
"totalPages": 5
}
}Send Instagram Message
/v1/instagram/messagesSend a direct message to an Instagram user by their page-scoped IGSID.
recipientIgsid must be a valid page-scoped Instagram user ID (IGSID), not a username. You can retrieve IGSIDs from GET /v1/instagram/conversations or via the Instagram inbox.Request Body
| Field | Type | Required | Description |
|---|---|---|---|
recipientIgsid | string | Yes | Page-scoped Instagram user ID (IGSID) |
message | string | Yes | Message text content (max 1000 chars) |
curl -X POST https://slide.synquic.com/api/v1/instagram/messages \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "recipientIgsid": "112233445566778", "message": "Hi! Thanks for reaching out. How can we help you today?" }'
const res = await fetch('https://slide.synquic.com/api/v1/instagram/messages', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ recipientIgsid: '112233445566778', message: 'Hi! Thanks for reaching out. How can we help you today?', }), }); const result = await res.json(); // { messageId: 'igmsg_01JXYZ...', status: 'sent' }
import requests res = requests.post( 'https://slide.synquic.com/api/v1/instagram/messages', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'recipientIgsid': '112233445566778', 'message': 'Hi! Thanks for reaching out.', } ) print(res.json()) # {'messageId': 'igmsg_01JXYZ...', 'status': 'sent'}
Get Instagram Insights
/v1/instagram/insightsRetrieve Instagram account analytics and performance metrics.
curl https://slide.synquic.com/api/v1/instagram/insights \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const res = await fetch('https://slide.synquic.com/api/v1/instagram/insights', { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' }, }); const insights = await res.json();
Response
{
"followerCount": 12450,
"followsDelta": 23,
"reach": 4821,
"profileViews": 1340,
"impressions": 9123,
"websiteClicks": 89,
"period": "last_7_days",
"updatedAt": "2026-03-24T00:00:00.000Z"
}WhatsApp API
Send WhatsApp template messages, retrieve message logs, and list approved templates. All messages are sent through your connected WhatsApp Business account and billed per Meta's category-based pricing (Marketing, Utility, Authentication, Service).
Send Template Message
/v1/whatsapp/send-templateSend an approved WhatsApp template message to a phone number. Supports all template types: text-only, media headers, quick-reply buttons, call-to-action buttons, and dynamic variables.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient phone number in E.164 format without + (e.g. "919876543210") |
templateName | string | Yes | Name of the approved template (e.g. "order_confirmation") |
languageCode | string | Yes | Template language code (e.g. "en", "en_US", "hi") |
components | array | No | Template components with dynamic variables (Meta Cloud API format) |
Components Format
Components follow the Meta Cloud API format. Each component specifies a type and parameters:
| Component Type | Usage | Example |
|---|---|---|
| header | Image, video, or document header | { "type": "header", "parameters": [{ "type": "image", "image": { "link": "https://..." } }] } |
| body | Dynamic variables in the template body | { "type": "body", "parameters": [{ "type": "text", "text": "John" }] } |
| button | Dynamic URL suffix or copy code | { "type": "button", "sub_type": "url", "index": "0", "parameters": [{ "type": "text", "text": "ORDER123" }] } |
# Send a simple text template with one body variable curl -X POST https://slide.synquic.com/api/v1/whatsapp/send-template \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "919876543210", "templateName": "order_confirmation", "languageCode": "en", "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "John" }, { "type": "text", "text": "ORD-12345" }, { "type": "text", "text": "₹2,499" } ] } ] }'
const response = await fetch('https://slide.synquic.com/api/v1/whatsapp/send-template', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ to: '919876543210', templateName: 'order_confirmation', languageCode: 'en', components: [ { type: 'body', parameters: [ { type: 'text', text: 'John' }, { type: 'text', text: 'ORD-12345' }, { type: 'text', text: '₹2,499' }, ], }, ], }), }); const data = await response.json(); // { wamid: "wamid.HBgM...", conversationId: "conv_01J...", status: "sent" }
import requests response = requests.post( 'https://slide.synquic.com/api/v1/whatsapp/send-template', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'to': '919876543210', 'templateName': 'order_confirmation', 'languageCode': 'en', 'components': [ { 'type': 'body', 'parameters': [ {'type': 'text', 'text': 'John'}, {'type': 'text', 'text': 'ORD-12345'}, {'type': 'text', 'text': '₹2,499'}, ], }, ], } ) data = response.json() # {'wamid': 'wamid.HBgM...', 'conversationId': 'conv_01J...', 'status': 'sent'}
Response
{
"wamid": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgSMjYxMDgx...",
"conversationId": "conv_01JXYZ...",
"status": "sent"
}400 Bad Request.Template Examples
// Text-only template (no variables)
{
"to": "919876543210",
"templateName": "welcome_message",
"languageCode": "en"
}// Template with image header + body variables
{
"to": "919876543210",
"templateName": "promo_offer",
"languageCode": "en",
"components": [
{
"type": "header",
"parameters": [
{ "type": "image", "image": { "link": "https://example.com/promo.jpg" } }
]
},
{
"type": "body",
"parameters": [
{ "type": "text", "text": "John" },
{ "type": "text", "text": "20%" }
]
}
]
}// OTP / Verification template (body + copy-code button)
{
"to": "919876543210",
"templateName": "verification",
"languageCode": "en",
"components": [
{
"type": "body",
"parameters": [{ "type": "text", "text": "654321" }]
},
{
"type": "button",
"sub_type": "url",
"index": "0",
"parameters": [{ "type": "text", "text": "654321" }]
}
]
}Get Message Logs
/v1/whatsapp/logsRetrieve paginated WhatsApp message logs for the organization. Includes inbound, outbound, delivery status, billing details, and message source attribution.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 50 | Results per page (max 100) |
direction | string | all | "inbound" or "outbound" |
status | string | all | "sent", "delivered", "read", or "failed" |
from | string | Start date (YYYY-MM-DD) | |
to | string | End date (YYYY-MM-DD) |
curl "https://slide.synquic.com/api/v1/whatsapp/logs?page=1&limit=20&direction=outbound" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const res = await fetch( 'https://slide.synquic.com/api/v1/whatsapp/logs?page=1&limit=20&direction=outbound', { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' } } ); const { data, meta } = await res.json();
Response
{
"data": [
{
"id": "msg_01JXYZ...",
"wamid": "wamid.HBgM...",
"direction": "outbound",
"type": "template",
"status": "delivered",
"content": { "templateName": "order_confirmation", "languageCode": "en" },
"templateName": "order_confirmation",
"templateCategory": "UTILITY",
"isTemplate": true,
"isBillable": true,
"billingAmountPaise": 12,
"source": "API",
"sourceName": "Production Key",
"timestamp": "2026-04-08T12:30:00.000Z",
"conversation": {
"id": "conv_01J...",
"customerWaId": "919876543210",
"customerName": "John Doe"
}
}
],
"meta": { "total": 142, "page": 1, "limit": 20, "totalPages": 8 }
}Source Values
| Source | Description |
|---|---|
MANUAL | Sent by a user from the Slide dashboard |
BOT | Sent by a WhatsApp automation/bot |
AUTOMATION | Sent by an automation workflow |
CAMPAIGN | Sent as part of a bulk campaign |
API | Sent via this Developer API |
INBOUND | Received from a customer |
SYSTEM | Platform system message (e.g. OTP verification) |
List Templates
/v1/whatsapp/templatesRetrieve all WhatsApp message templates for the organization, including their approval status, category, and language.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 25 | Results per page |
status | string | all | "APPROVED", "PENDING", or "REJECTED" |
category | string | all | "MARKETING", "UTILITY", or "AUTHENTICATION" |
curl "https://slide.synquic.com/api/v1/whatsapp/templates?status=APPROVED" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const res = await fetch( 'https://slide.synquic.com/api/v1/whatsapp/templates?status=APPROVED', { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' } } ); const templates = await res.json();
Response
{
"data": [
{
"id": "tpl_01JXYZ...",
"name": "order_confirmation",
"category": "UTILITY",
"language": "en",
"status": "APPROVED",
"components": [
{
"type": "BODY",
"text": "Hi {{1}}, your order {{2}} of {{3}} has been confirmed!"
}
],
"createdAt": "2026-01-10T08:00:00.000Z"
},
{
"id": "tpl_02ABCD...",
"name": "verification",
"category": "AUTHENTICATION",
"language": "en",
"status": "APPROVED",
"components": [
{
"type": "BODY",
"text": "Your verification code is {{1}}"
},
{
"type": "BUTTONS",
"buttons": [{ "type": "URL", "text": "Copy Code", "url": "https://..." }]
}
],
"createdAt": "2026-01-05T10:00:00.000Z"
}
],
"meta": { "total": 12, "page": 1, "limit": 25, "totalPages": 1 }
}Upload Header Media
/v1/whatsapp/header-media/uploadUpload an image, video, or document to use as a template header. Returns a media handle you can reference in the header component of a template send.
Request Body
Send as multipart/form-data with a single file field.
curl -X POST https://slide.synquic.com/api/v1/whatsapp/header-media/upload \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -F "file=@promo.jpg"
Response
{
"mediaId": "wamedia_01JXYZ...",
"url": "https://slide.synquic.com/media/wamedia_01JXYZ.jpg",
"mimeType": "image/jpeg"
}Get Conversation by Phone
/v1/whatsapp/conversations/by-phoneRetrieve the message history exchanged with a specific phone number. Returns the last N messages (max 50) in chronological order, oldest first.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
phone | string | Customer phone number in international format without + (required, e.g. "919876543210") | |
limit | number | 50 | Number of most recent messages to return (max 50) |
curl "https://slide.synquic.com/api/v1/whatsapp/conversations/by-phone?phone=919876543210&limit=50" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const res = await fetch( 'https://slide.synquic.com/api/v1/whatsapp/conversations/by-phone?phone=919876543210&limit=50', { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' } } ); const conversation = await res.json(); // conversation.data is chronological: oldest message first
import requests res = requests.get( 'https://slide.synquic.com/api/v1/whatsapp/conversations/by-phone', headers={'Authorization': 'Bearer sk_live_YOUR_API_KEY'}, params={'phone': '919876543210', 'limit': 50} ) conversation = res.json()
Response
{
"conversationId": "conv_01JXYZ...",
"customerWaId": "919876543210",
"customerName": "John Doe",
"data": [
{
"id": "msg_01JXYZ...",
"wamid": "wamid.HBgM...",
"direction": "outbound",
"type": "template",
"status": "read",
"content": { "templateName": "order_confirmation" },
"templateName": "order_confirmation",
"mediaUrl": null,
"mediaMimeType": null,
"errorCode": null,
"errorMessage": null,
"timestamp": "2026-04-08T12:30:00.000Z",
"createdAt": "2026-04-08T12:30:00.000Z"
},
{
"id": "msg_02ABCD...",
"wamid": "wamid.HBgN...",
"direction": "inbound",
"type": "text",
"status": "received",
"content": { "body": "Thanks! When will it arrive?" },
"templateName": null,
"mediaUrl": null,
"mediaMimeType": null,
"errorCode": null,
"errorMessage": null,
"timestamp": "2026-04-08T12:31:12.000Z",
"createdAt": "2026-04-08T12:31:12.000Z"
}
]
}List Campaigns
/v1/whatsapp/campaignsRetrieve a paginated list of WhatsApp campaigns with their status and headline counts.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Results per page (max 100) |
status | string | all | "DRAFT", "SCHEDULED", "RUNNING", "COMPLETED", or "CANCELLED" |
curl "https://slide.synquic.com/api/v1/whatsapp/campaigns?page=1&limit=20&status=COMPLETED" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "wac_01JXYZ...",
"name": "April Promo",
"status": "COMPLETED",
"templateId": "tpl_01JXYZ...",
"templateName": "promo_offer",
"totalRecipients": 5000,
"sentCount": 4980,
"deliveredCount": 4870,
"scheduledAt": null,
"createdAt": "2026-04-01T09:00:00.000Z"
}
],
"meta": { "total": 6, "page": 1, "limit": 20, "totalPages": 1 }
}Get Campaign
/v1/whatsapp/campaigns/:idRetrieve a single campaign with its full configuration and current status.
curl "https://slide.synquic.com/api/v1/whatsapp/campaigns/wac_01JXYZ" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Get Campaign Analytics
/v1/whatsapp/campaigns/:id/analyticsRetrieve delivery, read, click, reply, and conversion metrics for a campaign.
curl "https://slide.synquic.com/api/v1/whatsapp/campaigns/wac_01JXYZ/analytics" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"campaignId": "wac_01JXYZ...",
"totalRecipients": 5000,
"sent": 4980,
"delivered": 4870,
"read": 3120,
"clicked": 640,
"replied": 212,
"converted": 87,
"failed": 20,
"deliveryRate": 97.79,
"readRate": 64.07,
"clickRate": 13.14
}Create Campaign
/v1/whatsapp/campaignsCreate a DRAFT campaign with a manual phone list audience. The campaign does not send until you call the launch endpoint (or the scheduled time is reached after launch).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Campaign name (auto-generated if omitted) |
templateId | string | Yes | ID of an approved WhatsApp template |
phones | string[] | Yes | 1 to 10,000 phone numbers in international format without + (e.g. "919876543210") |
templateComponents | array | No | Template components with static variable values (Meta Cloud API format) |
variableMapping | object | No | Map template variables to per-recipient contact fields |
scheduledAt | string | No | ISO 8601 timestamp to schedule the send |
scheduleTimezone | string | No | IANA timezone for scheduledAt (e.g. "Asia/Kolkata") |
curl -X POST https://slide.synquic.com/api/v1/whatsapp/campaigns \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "April Promo", "templateId": "tpl_01JXYZ...", "phones": ["919876543210", "919876543211"], "scheduledAt": "2026-04-15T10:00:00.000Z", "scheduleTimezone": "Asia/Kolkata" }'
const res = await fetch('https://slide.synquic.com/api/v1/whatsapp/campaigns', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'April Promo', templateId: 'tpl_01JXYZ...', phones: ['919876543210', '919876543211'], scheduledAt: '2026-04-15T10:00:00.000Z', scheduleTimezone: 'Asia/Kolkata', }), }); const campaign = await res.json(); // { id: 'wac_01J...', status: 'DRAFT', totalRecipients: 2, ... }
import requests res = requests.post( 'https://slide.synquic.com/api/v1/whatsapp/campaigns', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'name': 'April Promo', 'templateId': 'tpl_01JXYZ...', 'phones': ['919876543210', '919876543211'], } ) campaign = res.json() print(campaign['id'], campaign['status']) # wac_01J... DRAFT
Response
{
"id": "wac_01JXYZ...",
"name": "April Promo",
"status": "DRAFT",
"templateId": "tpl_01JXYZ...",
"totalRecipients": 2,
"scheduledAt": "2026-04-15T10:00:00.000Z",
"scheduleTimezone": "Asia/Kolkata",
"createdAt": "2026-04-10T09:00:00.000Z"
}Launch Campaign
/v1/whatsapp/campaigns/:id/launchLaunch a DRAFT campaign. Sending is queued and processed asynchronously, the endpoint returns 202 Accepted immediately. If scheduledAt is set, the campaign moves to SCHEDULED and sends at that time.
curl -X POST "https://slide.synquic.com/api/v1/whatsapp/campaigns/wac_01JXYZ/launch" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"status": "queued",
"campaignId": "wac_01JXYZ..."
}Cancel Campaign
/v1/whatsapp/campaigns/:id/cancelCancel a scheduled or running campaign. Messages not yet sent are stopped and the wallet is refunded for all unsent recipients.
curl -X POST "https://slide.synquic.com/api/v1/whatsapp/campaigns/wac_01JXYZ/cancel" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"success": true,
"campaignId": "wac_01JXYZ...",
"refundedRecipients": 1240
}SMS API
Send transactional and promotional SMS, manage DLT templates and sender IDs, run bulk campaigns, and query delivery logs and stats. All sends go through your organization's registered DLT sender IDs and approved templates.
Send SMS
/v1/sms/sendSend an SMS to one or more recipients using a registered sender ID.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
senderId | string | Yes | Registered sender ID (e.g. "ACMEIN") |
to | string[] | Yes | Recipient phone numbers (min 1) |
message | string | Yes | Message text (must match the approved DLT template) |
templateId | string | No | DLT template ID for the message |
route | string | No | "transactional" or "promotional" (default "transactional") |
curl -X POST https://slide.synquic.com/api/v1/sms/send \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "senderId": "ACMEIN", "to": ["919876543210", "919876543211"], "message": "Your order ORD-12345 has shipped. Track it at https://acme.com/track", "route": "transactional" }'
const res = await fetch('https://slide.synquic.com/api/v1/sms/send', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ senderId: 'ACMEIN', to: ['919876543210'], message: 'Your order ORD-12345 has shipped.', route: 'transactional', }), }); const result = await res.json(); // { status: 'sent', recipients: 1, externalIds: ['ext_...'] }
import requests res = requests.post( 'https://slide.synquic.com/api/v1/sms/send', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'senderId': 'ACMEIN', 'to': ['919876543210'], 'message': 'Your order ORD-12345 has shipped.', 'route': 'transactional', } ) print(res.json()) # {'status': 'sent', 'recipients': 1, 'externalIds': ['ext_...']}
Response
{
"status": "sent",
"recipients": 2,
"externalIds": ["ext_01JXYZ...", "ext_02ABCD..."]
}List SMS Templates
/v1/sms/templatesRetrieve a paginated list of the organization's DLT SMS templates.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Results per page (max 100) |
status | string | all | Filter by template status (e.g. "APPROVED", "PENDING") |
curl "https://slide.synquic.com/api/v1/sms/templates?page=1&limit=20&status=APPROVED" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "smst_01JXYZ...",
"name": "Order Shipped",
"dltTemplateId": "1107160000000012345",
"body": "Your order {#var#} has shipped. Track it at {#var#}",
"route": "transactional",
"status": "APPROVED",
"createdAt": "2026-02-01T09:00:00.000Z"
}
],
"meta": { "total": 8, "page": 1, "limit": 20, "totalPages": 1 }
}List Sender IDs
/v1/sms/sendersRetrieve a paginated list of the organization's registered SMS sender IDs.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Results per page (max 100) |
curl "https://slide.synquic.com/api/v1/sms/senders?page=1&limit=20" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "smss_01JXYZ...",
"senderId": "ACMEIN",
"entityId": "1101230000000067890",
"status": "APPROVED",
"createdAt": "2026-01-20T09:00:00.000Z"
}
],
"meta": { "total": 2, "page": 1, "limit": 20, "totalPages": 1 }
}List SMS Campaigns
/v1/sms/campaignsRetrieve a paginated list of SMS campaigns.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Results per page (max 100) |
curl "https://slide.synquic.com/api/v1/sms/campaigns?page=1&limit=20" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "smsc_01JXYZ...",
"name": "Weekend Sale",
"status": "COMPLETED",
"route": "promotional",
"senderId": "ACMEIN",
"totalRecipients": 12000,
"sentCount": 11940,
"deliveredCount": 11512,
"scheduledAt": null,
"createdAt": "2026-03-28T09:00:00.000Z"
}
],
"meta": { "total": 4, "page": 1, "limit": 20, "totalPages": 1 }
}Get SMS Campaign
/v1/sms/campaigns/:idRetrieve a single SMS campaign with its full configuration and current counts.
curl "https://slide.synquic.com/api/v1/sms/campaigns/smsc_01JXYZ" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Create SMS Campaign
/v1/sms/campaignsCreate a DRAFT SMS campaign. The campaign does not send until you call the launch endpoint.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Campaign name |
messageBody | string | Yes | Message text (must match the approved DLT template) |
senderId | string | No | Registered sender ID to send from |
templateId | string | No | DLT template ID for the message |
route | string | No | "transactional" or "promotional" |
audienceType | string | No | Audience selection mode (e.g. all contacts, segment, manual list) |
audienceCriteria | object | No | Audience filter criteria for the selected audienceType |
scheduledAt | string | No | ISO 8601 timestamp to schedule the send |
curl -X POST https://slide.synquic.com/api/v1/sms/campaigns \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Weekend Sale", "messageBody": "Flat 20% off this weekend! Shop now: https://acme.com/sale", "senderId": "ACMEIN", "route": "promotional", "scheduledAt": "2026-04-12T04:30:00.000Z" }'
const res = await fetch('https://slide.synquic.com/api/v1/sms/campaigns', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ name: 'Weekend Sale', messageBody: 'Flat 20% off this weekend! Shop now: https://acme.com/sale', senderId: 'ACMEIN', route: 'promotional', }), }); const campaign = await res.json(); // { id: 'smsc_01J...', status: 'DRAFT', ... }
import requests res = requests.post( 'https://slide.synquic.com/api/v1/sms/campaigns', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'name': 'Weekend Sale', 'messageBody': 'Flat 20% off this weekend! Shop now: https://acme.com/sale', 'senderId': 'ACMEIN', 'route': 'promotional', } ) campaign = res.json() print(campaign['id'], campaign['status']) # smsc_01J... DRAFT
Response
{
"id": "smsc_01JXYZ...",
"name": "Weekend Sale",
"status": "DRAFT",
"route": "promotional",
"senderId": "ACMEIN",
"scheduledAt": "2026-04-12T04:30:00.000Z",
"createdAt": "2026-04-10T09:00:00.000Z"
}Launch SMS Campaign
/v1/sms/campaigns/:id/launchLaunch a DRAFT campaign. Sending is queued and processed asynchronously, the endpoint returns 202 Accepted immediately.
curl -X POST "https://slide.synquic.com/api/v1/sms/campaigns/smsc_01JXYZ/launch" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"status": "queued",
"campaignId": "smsc_01JXYZ..."
}Cancel SMS Campaign
/v1/sms/campaigns/:id/cancelCancel a scheduled or running SMS campaign. Messages not yet sent are stopped.
curl -X POST "https://slide.synquic.com/api/v1/sms/campaigns/smsc_01JXYZ/cancel" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"success": true,
"campaignId": "smsc_01JXYZ..."
}Get SMS Logs
/v1/sms/logsRetrieve paginated SMS message logs with direction, status, and date filters.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 50 | Results per page (max 100) |
direction | string | all | "inbound" or "outbound" |
status | string | all | Filter by delivery status (e.g. "sent", "delivered", "failed") |
start | string | Start date (ISO 8601) | |
end | string | End date (ISO 8601) |
curl "https://slide.synquic.com/api/v1/sms/logs?page=1&limit=50&direction=outbound&status=delivered" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "smsl_01JXYZ...",
"externalId": "ext_01JXYZ...",
"direction": "outbound",
"route": "transactional",
"status": "delivered",
"body": "Your order ORD-12345 has shipped.",
"senderId": "ACMEIN",
"templateId": "smst_01JXYZ...",
"source": "API",
"sentAt": "2026-04-08T12:30:00.000Z",
"deliveredAt": "2026-04-08T12:30:04.000Z",
"failedAt": null,
"errorCode": null,
"errorMessage": null,
"createdAt": "2026-04-08T12:30:00.000Z",
"phone": "919876543210",
"customerName": "John Doe"
}
],
"meta": { "total": 1420, "page": 1, "limit": 50, "totalPages": 29 }
}Get SMS Stats
/v1/sms/statsRetrieve daily aggregated SMS delivery stats for a date range.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
start | string | Start date (ISO 8601) | |
end | string | End date (ISO 8601) |
curl "https://slide.synquic.com/api/v1/sms/stats?start=2026-04-01&end=2026-04-30" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"dataPoints": [
{ "date": "2026-04-01", "sent": 480, "delivered": 462, "failed": 18 },
{ "date": "2026-04-02", "sent": 512, "delivered": 498, "failed": 14 }
],
"totals": {
"sent": 992,
"delivered": 960,
"failed": 32,
"deliveryRate": 96.77
}
}RCS API
Send rich RCS Business Messaging content, cards, carousels, and suggested actions, with automatic SMS fallback for handsets that do not support RCS. Manage bots, templates, campaigns, and delivery logs.
Send RCS Message
/v1/rcs/sendSend a single rich RCS message using an approved template, with an optional SMS fallback for non-RCS handsets.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient phone number |
templateId | string | Yes | ID of an approved RCS template |
rcsVariables | object | No | Key-value map of template variable values |
smsFallback | object | No | SMS fallback: { sender, message, templateId, route }, sent if the handset does not support RCS |
ttl | string | No | Time-to-live for the message before fallback or expiry (e.g. "3600s") |
curl -X POST https://slide.synquic.com/api/v1/rcs/send \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "919876543210", "templateId": "rcst_01JXYZ...", "rcsVariables": { "name": "John", "orderId": "ORD-12345" }, "smsFallback": { "sender": "ACMEIN", "message": "Your order ORD-12345 has shipped.", "route": "transactional" }, "ttl": "3600s" }'
const res = await fetch('https://slide.synquic.com/api/v1/rcs/send', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ to: '919876543210', templateId: 'rcst_01JXYZ...', rcsVariables: { name: 'John', orderId: 'ORD-12345' }, smsFallback: { sender: 'ACMEIN', message: 'Your order ORD-12345 has shipped.' }, }), }); const result = await res.json();
import requests res = requests.post( 'https://slide.synquic.com/api/v1/rcs/send', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'to': '919876543210', 'templateId': 'rcst_01JXYZ...', 'rcsVariables': {'name': 'John', 'orderId': 'ORD-12345'}, 'smsFallback': {'sender': 'ACMEIN', 'message': 'Your order ORD-12345 has shipped.'}, } ) print(res.json())
List RCS Bots
/v1/rcs/botsRetrieve the RCS bots provisioned for your organization.
curl "https://slide.synquic.com/api/v1/rcs/bots" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "rcsb_01JXYZ...",
"name": "Acme Store",
"status": "APPROVED",
"createdAt": "2026-02-15T09:00:00.000Z"
}
]
}List RCS Templates
/v1/rcs/templatesRetrieve a paginated list of RCS templates.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Results per page (max 100) |
curl "https://slide.synquic.com/api/v1/rcs/templates?page=1&limit=20" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Create RCS Campaign
/v1/rcs/campaignsCreate and dispatch a bulk RCS campaign. The bulk send is accepted and processed asynchronously, the endpoint returns 202 Accepted.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
botId | string | Yes | ID of the RCS bot to send from |
templateId | string | Yes | ID of an approved RCS template |
campaignName | string | No | Campaign name |
numbers | string[] | No | Recipient phone numbers |
country | string | No | Recipient country code (default "IN") |
removeDuplicate | boolean | No | Deduplicate the recipient list before sending |
ttl | string | No | Time-to-live before fallback or expiry |
scheduleCampaign | boolean | No | Schedule instead of sending immediately |
scheduledAt | string | No | ISO 8601 timestamp for the scheduled send |
fallback | boolean | No | Enable SMS fallback for non-RCS handsets |
fallbackMessage | string | No | SMS fallback message text |
curl -X POST https://slide.synquic.com/api/v1/rcs/campaigns \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "botId": "rcsb_01JXYZ...", "templateId": "rcst_01JXYZ...", "campaignName": "April Launch", "numbers": ["919876543210", "919876543211"], "country": "IN", "removeDuplicate": true, "fallback": true, "fallbackMessage": "New arrivals are live! Shop now: https://acme.com" }'
const res = await fetch('https://slide.synquic.com/api/v1/rcs/campaigns', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ botId: 'rcsb_01JXYZ...', templateId: 'rcst_01JXYZ...', campaignName: 'April Launch', numbers: ['919876543210', '919876543211'], removeDuplicate: true, }), }); // 202 Accepted, bulk send queued
import requests res = requests.post( 'https://slide.synquic.com/api/v1/rcs/campaigns', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'botId': 'rcsb_01JXYZ...', 'templateId': 'rcst_01JXYZ...', 'campaignName': 'April Launch', 'numbers': ['919876543210', '919876543211'], } ) print(res.status_code) # 202
List RCS Campaigns
/v1/rcs/campaignsRetrieve a paginated list of RCS campaigns.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Results per page (max 100) |
curl "https://slide.synquic.com/api/v1/rcs/campaigns?page=1&limit=20" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Get RCS Logs
/v1/rcs/logsRetrieve paginated RCS message logs including delivery status and fallback outcomes.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 50 | Results per page (max 100) |
curl "https://slide.synquic.com/api/v1/rcs/logs?page=1&limit=50" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Get RCS Stats
/v1/rcs/statsRetrieve aggregated RCS delivery stats for a date range.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
start | string | Start date (ISO 8601) | |
end | string | End date (ISO 8601) |
curl "https://slide.synquic.com/api/v1/rcs/stats?start=2026-04-01&end=2026-04-30" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
OTP API
Server-side phone verification: send one-time passwords over SMS or WhatsApp, verify them, and prove verification to your backend with single-use access tokens. OTP length, expiry, resend cooldown, and delivery channels are configured on an OTP widget in the dashboard, the API references that widget by widgetId.
Send OTP
/v1/otp/sendSend a one-time password to a phone number using an OTP widget's configuration. Returns a requestId used for retry and verification.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
widgetId | string | Yes | ID of the OTP widget configured in the dashboard |
identifier | string | Yes | Phone number to verify (e.g. "+919876543210") |
curl -X POST https://slide.synquic.com/api/v1/otp/send \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "widgetId": "wgt_01JXYZ...", "identifier": "+919876543210" }'
const res = await fetch('https://slide.synquic.com/api/v1/otp/send', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ widgetId: 'wgt_01JXYZ...', identifier: '+919876543210' }), }); const { requestId } = await res.json();
import requests res = requests.post( 'https://slide.synquic.com/api/v1/otp/send', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={'widgetId': 'wgt_01JXYZ...', 'identifier': '+919876543210'} ) request_id = res.json()['requestId']
Response
{
"requestId": "otpreq_01JXYZ..."
}Retry OTP
/v1/otp/retryResend the OTP for an existing request, optionally over a different channel. Respects the widget's resend cooldown and maximum resend count.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
requestId | string | Yes | The requestId returned by POST /otp/send |
channel | string | No | Delivery channel override (e.g. "sms", "whatsapp"), must be enabled on the widget |
curl -X POST https://slide.synquic.com/api/v1/otp/retry \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "requestId": "otpreq_01JXYZ...", "channel": "whatsapp" }'
Response
{
"requestId": "otpreq_01JXYZ..."
}Verify OTP
/v1/otp/verifyVerify the OTP entered by the user. On success, returns a JWT accessToken that proves the verification. Returns 400 if the code is invalid, expired, or the request is blocked after too many attempts.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
requestId | string | Yes | The requestId returned by POST /otp/send |
otp | string | Yes | The one-time password entered by the user |
curl -X POST https://slide.synquic.com/api/v1/otp/verify \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "requestId": "otpreq_01JXYZ...", "otp": "123456" }'
const res = await fetch('https://slide.synquic.com/api/v1/otp/verify', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ requestId: 'otpreq_01JXYZ...', otp: '123456' }), }); const { accessToken } = await res.json(); // Pass accessToken to your backend, then consume it with POST /otp/verify-token
import requests res = requests.post( 'https://slide.synquic.com/api/v1/otp/verify', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={'requestId': 'otpreq_01JXYZ...', 'otp': '123456'} ) access_token = res.json()['accessToken']
Response
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Verify Access Token
/v1/otp/verify-tokenConfirm server-side that an accessToken represents a real, recent verification. The token is single-use: this call consumes it, a second call with the same token fails.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
accessToken | string | Yes | The JWT returned by POST /otp/verify |
curl -X POST https://slide.synquic.com/api/v1/otp/verify-token \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "accessToken": "eyJhbGciOiJIUzI1NiIs..." }'
Response
{
"verified": true,
"identifier": "+919876543210",
"verifiedAt": "2026-04-08T12:31:12.000Z"
}accessToken to your backend. Your backend calls POST /v1/otp/verify-token once to confirm and consume it before trusting the phone number.Get OTP Logs
/v1/otp/logsRetrieve paginated OTP request logs, optionally filtered by widget and status.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
widgetId | string | all | Filter by OTP widget |
status | string | all | Filter by request status (e.g. "sent", "verified", "expired", "blocked") |
page | number | 1 | Page number |
limit | number | 50 | Results per page (max 100) |
curl "https://slide.synquic.com/api/v1/otp/logs?widgetId=wgt_01JXYZ&status=verified&page=1&limit=50" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "otpreq_01JXYZ...",
"widgetId": "wgt_01JXYZ...",
"identifier": "+919876543210",
"channel": "sms",
"status": "verified",
"attempts": 1,
"resends": 0,
"sentAt": "2026-04-08T12:30:00.000Z",
"verifiedAt": "2026-04-08T12:31:12.000Z"
}
],
"meta": { "total": 940, "page": 1, "limit": 50, "totalPages": 19 }
}Get OTP Analytics
/v1/otp/analyticsRetrieve success and failure rates and delivery analytics for an OTP widget.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
widgetId | string | all | Filter analytics to a single OTP widget |
curl "https://slide.synquic.com/api/v1/otp/analytics?widgetId=wgt_01JXYZ" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"totalSent": 5230,
"totalVerified": 4870,
"totalExpired": 240,
"totalBlocked": 46,
"successRate": 93.12,
"channelBreakdown": {
"sms": { "sent": 4100, "verified": 3810 },
"whatsapp": { "sent": 1130, "verified": 1060 }
}
}Shopify API
The Shopify API provides read-only access to products, collections, orders, and discount codes from your connected Shopify store. Requires an active Shopify integration in your organization.
404 Not Found.Search Products
/v1/shopify/products/searchFull-text search across product title, tags, vendor, and type.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | Search query (required) | |
limit | number | 10 | Max results (max 50) |
curl "https://slide.synquic.com/api/v1/shopify/products/search?q=sneakers&limit=5" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "gid://shopify/Product/123456",
"title": "Classic Sneakers",
"vendor": "Nike",
"productType": "Footwear",
"tags": ["sneakers", "sale"],
"variants": [
{ "id": "gid://shopify/ProductVariant/789", "title": "Size 10", "price": "89.99", "inventoryQuantity": 24 }
]
}
],
"meta": { "count": 1, "query": "sneakers" }
}List Product Types
/v1/shopify/products/typesGet all unique product types (categories) sorted A Z.
curl "https://slide.synquic.com/api/v1/shopify/products/types" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": ["Accessories", "Footwear", "T-Shirts", "Watches"],
"meta": { "count": 4 }
}Get Product
/v1/shopify/products/:idGet full product detail including all variants, options, and description.
curl "https://slide.synquic.com/api/v1/shopify/products/gid://shopify/Product/123456" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
List Collections
/v1/shopify/collectionsList all custom and smart collections sorted A Z.
curl "https://slide.synquic.com/api/v1/shopify/collections" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{ "id": "gid://shopify/Collection/111", "title": "Summer Sale", "handle": "summer-sale", "productsCount": 24 }
],
"meta": { "count": 1 }
}Get Collection Products
/v1/shopify/collections/:id/productsList products in a specific collection.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 50 | Max results (max 250) |
curl "https://slide.synquic.com/api/v1/shopify/collections/gid://shopify/Collection/111/products?limit=10" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Get Order Status
/v1/shopify/orders/statusLook up an order by order number (e.g. #1001) or customer email.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
identifier | string | Order number (e.g. "#1001") or customer email (required) |
curl "https://slide.synquic.com/api/v1/shopify/orders/status?identifier=%231001" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"orderNumber": "#1001",
"financialStatus": "paid",
"fulfillmentStatus": "fulfilled",
"totalPrice": "129.99",
"currency": "INR",
"lineItems": [
{ "title": "Classic Sneakers", "quantity": 1, "price": "89.99" },
{ "title": "Sports Socks", "quantity": 2, "price": "20.00" }
],
"tracking": {
"carrier": "BlueDart",
"number": "BD123456789",
"url": "https://bluedart.com/track/BD123456789"
},
"createdAt": "2026-03-20T10:30:00.000Z"
}Get Customer Orders
/v1/shopify/customers/ordersGet a customer's order history by phone number or email.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
identifier | string | Phone (E.164 or local) or email (required) |
curl "https://slide.synquic.com/api/v1/shopify/customers/orders?identifier=%2B919876543210" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
const res = await fetch( 'https://slide.synquic.com/api/v1/shopify/customers/orders?identifier=user@example.com', { headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY' } } ); const { customer, orders } = await res.json();
Validate Discount Code
/v1/shopify/discounts/validateValidate a discount or promo code in real time.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
code | string | Discount code to validate (required) |
curl "https://slide.synquic.com/api/v1/shopify/discounts/validate?code=SAVE20" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"valid": true,
"code": "SAVE20",
"type": "percentage",
"value": 20,
"minimumOrderAmount": "500.00",
"expiresAt": "2026-06-30T23:59:59.000Z",
"usageCount": 142,
"usageLimit": 1000
}Voice API
The Voice API provides programmatic access to AI voice calling manage agents, initiate outbound calls, retrieve call history with transcripts, and access recordings. Powered by enterprise SIP infrastructure.
List Voice Agents
/v1/voice/agentsList all AI voice agents configured for your organization.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (1-indexed) |
limit | number | 20 | Results per page (max 100) |
curl "https://slide.synquic.com/api/v1/voice/agents?page=1&limit=10" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "va_01JXYZ...",
"name": "Sales Assistant",
"description": "Handles inbound sales inquiries",
"language": "en-US",
"sttProvider": "DEEPGRAM",
"ttsProvider": "ELEVENLABS",
"llmProvider": "OPENAI",
"llmModel": "gpt-4o-mini",
"isActive": true,
"createdAt": "2026-03-01T10:00:00.000Z"
}
],
"meta": { "total": 3, "page": 1, "limit": 10, "totalPages": 1 }
}Get Voice Agent
/v1/voice/agents/:idGet full agent configuration including persona, language, and provider settings.
curl "https://slide.synquic.com/api/v1/voice/agents/va_01JXYZ" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
List Voice Calls
/v1/voice/callsList voice calls with optional filtering by status and direction.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Results per page (max 100) |
status | string | Filter: INITIATED, RINGING, ANSWERED, COMPLETED, FAILED, NO_ANSWER, BUSY, CANCELLED | |
direction | string | Filter: INBOUND or OUTBOUND |
curl "https://slide.synquic.com/api/v1/voice/calls?status=COMPLETED&direction=OUTBOUND&limit=10" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": [
{
"id": "vc_01JXYZ...",
"agentId": "va_01JXYZ...",
"direction": "OUTBOUND",
"callerNumber": "+911234567890",
"calledNumber": "+919876543210",
"status": "COMPLETED",
"durationSeconds": 142,
"sentiment": "POSITIVE",
"summary": "Customer confirmed appointment for Thursday.",
"wasConfirmed": true,
"billedAmountPaise": 3200,
"startedAt": "2026-03-24T10:00:00.000Z",
"endedAt": "2026-03-24T10:02:22.000Z"
}
],
"meta": { "total": 56, "page": 1, "limit": 10, "totalPages": 6 }
}Initiate Outbound Call
/v1/voice/calls/outboundInitiate an AI-powered outbound voice call to a phone number.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
agentId | string | Yes | ID of the voice agent to use |
toNumber | string | Yes | Phone number to call (E.164 format preferred) |
callContext | object | No | Key-value context passed to the AI agent during the call |
curl -X POST "https://slide.synquic.com/api/v1/voice/calls/outbound" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "va_01JXYZ...", "toNumber": "+919876543210", "callContext": { "customerName": "Rahul Sharma", "appointmentDate": "2026-04-10", "purpose": "appointment_confirmation" } }'
const res = await fetch('https://slide.synquic.com/api/v1/voice/calls/outbound', { method: 'POST', headers: { 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, body: JSON.stringify({ agentId: 'va_01JXYZ...', toNumber: '+919876543210', callContext: { customerName: 'Rahul Sharma', purpose: 'appointment_confirmation' }, }), }); const call = await res.json(); // { id: 'vc_01J...', status: 'INITIATED', direction: 'OUTBOUND', ... }
import requests res = requests.post( 'https://slide.synquic.com/api/v1/voice/calls/outbound', headers={ 'Authorization': 'Bearer sk_live_YOUR_API_KEY', 'Content-Type': 'application/json', }, json={ 'agentId': 'va_01JXYZ...', 'toNumber': '+919876543210', 'callContext': {'customerName': 'Rahul Sharma', 'purpose': 'appointment_confirmation'}, } ) call = res.json() print(call['id'], call['status']) # vc_01J... INITIATED
Get Call Details
/v1/voice/calls/:idGet full call details including transcription, sentiment, and cost breakdown.
curl "https://slide.synquic.com/api/v1/voice/calls/vc_01JXYZ" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"id": "vc_01JXYZ...",
"agentId": "va_01JXYZ...",
"direction": "OUTBOUND",
"status": "COMPLETED",
"callerNumber": "+911234567890",
"calledNumber": "+919876543210",
"callerName": "Rahul Sharma",
"durationSeconds": 142,
"sentiment": "POSITIVE",
"summary": "Customer confirmed the appointment for Thursday 10 AM.",
"wasConfirmed": true,
"wasBooked": false,
"transcription": [
{ "role": "agent", "content": "Hello! This is a call from Acme Corp regarding your appointment.", "timestamp": "2026-03-24T10:00:03.000Z" },
{ "role": "user", "content": "Yes, I was expecting this call.", "timestamp": "2026-03-24T10:00:08.000Z" }
],
"billedAmountPaise": 3200,
"startedAt": "2026-03-24T10:00:00.000Z",
"endedAt": "2026-03-24T10:02:22.000Z"
}Get Call Recording
/v1/voice/calls/:id/recordingGet a pre-signed URL for the call recording audio. URL expires in 1 hour.
curl "https://slide.synquic.com/api/v1/voice/calls/vc_01JXYZ/recording" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"data": {
"url": "https://s3.ap-south-1.amazonaws.com/recordings/vc_01JXYZ.ogg?X-Amz-Expires=3600&..."
}
}404 if the call has no recording.Get Voice Analytics
/v1/voice/analyticsGet aggregated voice call metrics for a date range.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
from | string | Start date (ISO 8601, required) | |
to | string | End date (ISO 8601, required) |
curl "https://slide.synquic.com/api/v1/voice/analytics?from=2026-03-01&to=2026-03-31" \ -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response
{
"totalCalls": 234,
"completedCalls": 198,
"failedCalls": 12,
"noAnswerCalls": 24,
"avgDurationSeconds": 127,
"totalDurationSeconds": 25146,
"sentimentBreakdown": {
"POSITIVE": 142,
"NEUTRAL": 38,
"NEGATIVE": 18
},
"totalCostPaise": 748800,
"confirmedCount": 156,
"bookedCount": 42
}App-to-App Voice API
Voice calling between two of your own app users, in both directions, with no phone numbers and no AI agent. Both parties connect over WebRTC from your apps into a room we create, so neither side ever sees the other’s number and there is no per-minute telephony cost on either leg.
Overview & Call Flow
Three objects. An identity is one person in your marketplace, keyed by your own id. A session pairs two identities for one booking and is the only thing that grants permission to call. A call is one conversation on that session.
SETUP (once per booking, from your backend) POST /v1/voice/app/identities pro_991 role=PRO POST /v1/voice/app/identities cust_5521 role=CUSTOMER POST /v1/voice/app/sessions externalRef=BK-8842, pairs the two OUTBOUND (your Pro taps "Call") Pro app --> your backend --> POST /v1/voice/app/calls { externalRef, fromExternalId: "pro_991" } <-- { callId, roomId, token, voiceServerUrl } Pro app connects to voiceServerUrl with token (LiveKit client SDK) Meanwhile we notify the callee: webhook voice.app.call.incoming --> your backend (always) push to registered devices (if registered) socket voice:app.call.incoming (if app foregrounded) ANSWER Customer app --> your backend --> POST /v1/voice/app/calls/:id/accept { asExternalId: "cust_5521" } <-- { token, voiceServerUrl } Customer app connects. Both are in the room. Connected. END POST /v1/voice/app/calls/:id/end { asExternalId } webhook voice.app.call.completed --> your backend
The direction is symmetric. A Customer calling a Pro is the same request withfromExternalId set to the customer. The callee is always derived from the session, never supplied by the caller.
Create or Update an Identity
/v1/voice/app/identitiesRegister one participant, keyed by your own id. Idempotent: safe to replay for a full roster sync.
Body
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Your id for this person. Stable for the life of the account. |
role | string | Yes | PRO or CUSTOMER |
displayName | string | No | Shown as the caller name on push notifications. |
phone | string | No | E.164. Stored encrypted, used only for the PSTN fallback leg. Omit it if you would rather we never hold it. |
curl -X POST https://api.synquic.com/api/v1/voice/app/identities \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "externalId": "pro_991", "role": "PRO", "displayName": "Ramesh K." }'
Get an Identity
/v1/voice/app/identities/:externalIdIncludes reachability, so you can hide the call button when a call could not ring.
{
"id": "vai_3c81",
"externalId": "pro_991",
"role": "PRO",
"displayName": "Ramesh K.",
"isActive": true,
"lastSeenAt": "2026-08-22T10:09:00.000Z",
"deviceCount": 2,
"reachable": true
}Register a Device
/v1/voice/app/identities/:externalId/devicesOptional. Register a push target so we can ring the device directly.
isVoip: true. A backgrounded app can only raise the native incoming-call screen from a VoIP push. Register a plain notification token and the device can be messaged but not rung.| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | FCM registration token, or APNs VoIP token. |
platform | string | Yes | ios or android |
isVoip | boolean | No | Defaults to true on iOS. |
Create a Calling Session
/v1/voice/app/sessionsPair a Pro and a Customer for one booking. This is what authorises them to call each other.
| Field | Type | Required | Description |
|---|---|---|---|
externalRef | string | Yes | Your booking / job / order id. Unique per organization. |
proExternalId | string | Yes | Must already exist with role PRO. |
customerExternalId | string | Yes | Must already exist with role CUSTOMER. |
expiresAt | string | No | ISO 8601. Defaults to 24 hours out, capped at 90 days. |
metadata | object | No | Echoed back on every webhook for this session. |
externalRef. Replaying it extends the booking instead of creating a second session, and reactivates one you previously revoked.End a Session
/v1/voice/app/sessions/:externalRefThe booking finished or was cancelled. No new calls can be placed; a call already in progress is left to finish.
Place a Call
/v1/voice/app/callsCreates the room, returns the caller's join token, and notifies the callee.
| Field | Type | Required | Description |
|---|---|---|---|
externalRef | string | Yes | The session both parties belong to. |
fromExternalId | string | Yes | Who is placing the call. Must be one of the two identities on the session. |
toExternalId. The callee is derived from the session: letting the caller name the callee would turn a booking-scoped permission into an open dialler.{
"callId": "apc_7f31c9a2",
"roomId": "app-org_1a2b-7f31c9a2",
"status": "RINGING",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"voiceServerUrl": "wss://livekit.synquic.com",
"tokenExpiresIn": 300,
"ringTimeoutSeconds": 45,
"callee": { "externalId": "cust_5521", "displayName": "Priya S." }
}Hand token and voiceServerUrl to the caller’s app and connect with the LiveKit client SDK. If nobody answers withinringTimeoutSeconds the call is markedMISSED automatically. Returns 409 if the session already has a live call.
Accept a Call
/v1/voice/app/calls/:callId/acceptThe callee picks up and receives their join token.
| Field | Type | Required | Description |
|---|---|---|---|
asExternalId | string | Yes | The callee. Only they can accept. |
Tokens are short-lived by design. On network reconnect during a long call, request a fresh one with POST /v1/voice/app/calls/:callId/token.
End or Reject a Call
/v1/voice/app/calls/:callId/endEither party hangs up. Safe to call twice - both apps racing to report the hang-up is normal.
/v1/voice/app/calls/:callId/rejectThe callee declines a ringing call.
| Status | Meaning |
|---|---|
RINGING | Room created, callee notified, nobody connected yet. |
ACCEPTED | Callee joined. Both parties connected. |
COMPLETED | A connected call ended normally. |
CANCELLED | Hung up while still ringing. Never connected. |
REJECTED | The callee explicitly declined. |
MISSED | Nobody answered before the ring timeout. |
FAILED | The room could not be created. |
Webhooks
Subscribe at Dashboard → Settings → Webhooks or via the Webhooks API. voice.app.call.incoming is the important one: it is not an after-the-fact notification, it is how you learn to ring your user.
| Event | When |
|---|---|
voice.app.call.incoming | Someone is calling one of your users. Deliver it to the callee so their app can ring. |
voice.app.call.answered | The callee accepted. Both parties connected. |
voice.app.call.completed | A connected call ended. Includes talk time. |
voice.app.call.missed | Nobody answered before the ring timeout. |
voice.app.call.rejected | The callee declined. |
voice.app.session.expired | A session reached its expiry. The pair can no longer call. |
{
"id": "evt_01JXYZ...",
"type": "voice.app.call.incoming",
"createdAt": "2026-08-22T10:11:12.000Z",
"data": {
"callId": "apc_7f31c9a2",
"sessionRef": "BK-8842",
"roomId": "app-org_1a2b-7f31c9a2",
"direction": "PRO_TO_CUSTOMER",
"from": { "externalId": "pro_991", "displayName": "Ramesh K.", "role": "PRO" },
"to": { "externalId": "cust_5521", "displayName": "Priya S.", "role": "CUSTOMER" },
"ringTimeoutSeconds": 45,
"startedAt": "2026-08-22T10:11:12.000Z"
}
}WebSocket Events
Slide provides real-time event streaming via Socket.IO for live call monitoring and transcript streaming. Connect to the WebSocket server to receive voice call lifecycle events and live transcription.
Connection
Connect using the Socket.IO client library. The WebSocket server runs on the same host as the REST API.
import { io } from 'socket.io-client'; const socket = io('https://slide.synquic.com', { transports: ['websocket'], autoConnect: true, }); // Join your organization room to receive events socket.emit('joinOrganization', 'your-org-id'); // Subscribe to a specific call for live transcript socket.emit('joinCall', 'call-id'); // Unsubscribe when done socket.emit('leaveCall', 'call-id'); socket.emit('leaveOrganization', 'your-org-id');
import socketio sio = socketio.Client() sio.connect('https://slide.synquic.com', transports=['websocket']) # Join organization room sio.emit('joinOrganization', 'your-org-id') # Listen for events @sio.on('voice:call.initiated') def on_call_initiated(data): print(f"Call started: {data['callId']}") @sio.on('voice:transcript.chunk') def on_transcript(data): print(f"[{data['role']}]: {data['content']}")
Voice Call Events
Voice call lifecycle events are emitted to the organization room (org_{orgId}). Subscribe by emitting joinOrganization with your org ID.
| Event | Payload | Description |
|---|---|---|
voice:call.initiated | { callId, agentId, direction, calledNumber, status } | New call created and dispatched |
voice:call.ringing | { callId, status } | Call is ringing (voice room started) |
voice:call.answered | { callId, status, answeredAt } | Call was answered by the recipient |
voice:call.ended | { callId, status, durationSeconds, sentiment, summary } | Call completed or failed |
// Listen for call lifecycle events socket.on('voice:call.initiated', (data) => { console.log('Call started:', data.callId, data.direction); }); socket.on('voice:call.answered', (data) => { console.log('Call answered:', data.callId); // Join call room for live transcript socket.emit('joinCall', data.callId); }); socket.on('voice:call.ended', (data) => { console.log('Call ended:', data.callId, data.sentiment, data.summary); socket.emit('leaveCall', data.callId); });
Live Transcript Streaming
Real-time transcript chunks are emitted to the call room (call_{callId}). Subscribe by emitting joinCall with the call ID. Each chunk contains a single utterance from the AI agent or the human caller.
| Event | Payload | Description |
|---|---|---|
voice:transcript.chunk | { role, content, timestamp } | Single transcript utterance |
| Field | Type | Description |
|---|---|---|
role | "agent" | "user" | Who spoke the AI agent or the human caller |
content | string | Transcribed text of the utterance |
timestamp | string | ISO 8601 timestamp of when it was spoken |
// Subscribe to live transcript for a specific call socket.emit('joinCall', 'vc_01JXYZ...'); socket.on('voice:transcript.chunk', (chunk) => { const speaker = chunk.role === 'agent' ? 'AI' : 'Caller'; console.log(`[${speaker}] ${chunk.content}`); // [AI] Hello! This is a call from Acme Corp regarding your appointment. // [Caller] Yes, I was expecting this call. }); // Clean up when done socket.emit('leaveCall', 'vc_01JXYZ...');
GET /v1/voice/calls/:id in the transcription field.Webhooks
Everything above is you calling us. Webhooks are the other direction: we POST a signed JSON payload to a URL you own whenever something happens on your account, so you do not have to poll. Register endpoints at slide.synquic.com/dashboard/webhooks, or over this API with a key holding the webhooks:write scope.
An account can hold up to 50 endpoints, and each one independently chooses which events it wants. That is what makes tools like Zapier work correctly: every Zap subscribes its own URL when you turn it on and removes it when you turn it off, so several can be live at once without interfering with each other.
Zapier, n8n & direct
Three ways to consume these events. All three drive the same endpoints below, so an account can run any combination at once — each connection registers its own webhook endpoint and they never interfere.
| Integration | How it connects | Status |
|---|---|---|
| n8n | Install n8n-nodes-slide-synquicfrom Settings → Community Nodes. A trigger node for every event and an action node for sending. | Available now |
| Direct webhooks | Register an endpoint yourself and receive signed POSTs. Works with Make, Pipedream, or your own server. | Available now |
| Zapier | Triggers for every event plus actions for sending messages and managing contacts. | In review |
Payload format
Every delivery has the same envelope regardless of event type. The event-specific data is always under data, and type tells you what it is, so you can route on one field rather than sniffing the body shape.
{
"id": "evt_9f2c4b1e7a3d5f8c0b2e4a6d8f1c3e5a",
"type": "contact.created",
"createdAt": "2026-08-20T10:11:12.000Z",
"accountId": "org_9f2c4b1e",
"livemode": true,
"data": {
"contact": {
"id": "ct_9f2c4b1e",
"name": "Priya Sharma",
"email": "priya@example.com",
"phone": "+919876543210",
"tags": ["newsletter"],
"lifecycleStageId": "stg_lead",
"createdAt": "2026-08-20T10:11:12.000Z"
},
"source": "widget"
}
}Headers
| Header | Description |
|---|---|
X-Slide-Event-Id | Unique id for this event. Stable across retries, so use it to make your handler idempotent. |
X-Slide-Event-Type | The event type, identical to the envelope’s type field. |
X-Slide-Signature | HMAC signature, described below. Always verify it. |
X-Slide-Webhook-Id | Which of your endpoints this delivery was sent to. |
X-Slide-Delivery-Attempt | Attempt counter, starting at 1. |
X-Slide-Event-Id. Retries of a delivery reuse it too. Storing processed ids and skipping duplicates is the correct way to handle a retry after your server timed out but had already done the work.Verifying the signature
Your endpoint URL is not a secret, so anyone who learns it could post to it. Every delivery is signed with the secret shown once when you created the endpoint. The header looks like this:
X-Slide-Signature: t=1755683472,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
t is the Unix timestamp of the delivery and v1 is HMAC-SHA256 over the string "{t}.{raw request body}", hex encoded, keyed with your signing secret.
express.raw, not express.json, on this route.const crypto = require('crypto'); const express = require('express'); const app = express(); const SECRET = process.env.SLIDE_WEBHOOK_SECRET; // whsec_... // express.raw, NOT express.json — we need the exact bytes we were sent. app.post('/webhooks/slide', express.raw({ type: 'application/json' }), (req, res) => { const header = req.get('X-Slide-Signature') || ''; const rawBody = req.body.toString('utf8'); const parts = Object.fromEntries( header.split(',').map((p) => { const i = p.indexOf('='); return [p.slice(0, i).trim(), p.slice(i + 1).trim()]; }), ); // Reject anything older than 5 minutes, so a captured delivery cannot be // replayed indefinitely. The timestamp is inside the signed string, so it // cannot be edited without breaking the signature. const drift = Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t)); if (!parts.t || drift > 300) return res.status(400).send('stale'); const expected = crypto .createHmac('sha256', SECRET) .update(`${parts.t}.${rawBody}`, 'utf8') .digest(); const received = Buffer.from(parts.v1 || '', 'hex'); if ( received.length !== expected.length || !crypto.timingSafeEqual(received, expected) ) { return res.status(400).send('bad signature'); } const event = JSON.parse(rawBody); // Acknowledge FIRST, then do the work. We time out after 10 seconds. res.sendStatus(200); handleEvent(event).catch(console.error); });
import hashlib, hmac, time def verify_slide_signature(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool: """raw_body must be the exact bytes received, never a re-serialised dict.""" parts = dict( p.strip().split("=", 1) for p in header.split(",") if "=" in p ) timestamp = parts.get("t") signature = parts.get("v1") if not timestamp or not signature: return False if abs(int(time.time()) - int(timestamp)) > tolerance: return False expected = hmac.new( secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, signature)
Retries and failure handling
Return any 2xx status to acknowledge a delivery. Acknowledge before doing slow work: we time out after 10 seconds and treat that as a failure. Anything that is not a 2xx is retried on this schedule, up to 7 attempts spanning about 8 hours 45 minutes.
| Attempt | Sent after |
|---|---|
| 1 | Immediately |
| 2 | 10 seconds later |
| 3 | 1 minute later |
| 4 | 5 minutes later |
| 5 | 30 minutes later |
| 6 | 2 hours later |
| 7 | 6 hours later |
After the final attempt the delivery is marked exhausted. You can inspect and replay it from the delivery log, in the dashboard or over the API.
Returning 410 Gone is treated as an explicit unsubscribe: we delete the endpoint immediately rather than retrying. This is the REST Hooks convention and is how Zapier cleans up after a Zap is turned off.
Event catalogue
Subscribe to "*" to receive everything, including events added later. Call GET /v1/webhooks/events for this list with a sample payload attached to each entry.
| Event | Fires when |
|---|---|
contact.created | A contact is added to the unified contacts hub, from any channel or import. |
contact.updated | An existing contact changes. data.changedFields lists exactly what changed. |
contact.lifecycle_changed | A contact moves between lifecycle stages, for example Lead to Customer. |
form.submitted | A visitor submits a form, popup, or landing page. |
whatsapp.message.received | An inbound WhatsApp message arrives from a customer. |
whatsapp.message.status | A message you sent is marked sent, delivered, read, or failed. |
instagram.message.received | An inbound Instagram direct message arrives. |
sms.message.received | An inbound SMS arrives on one of your numbers. |
voice.call.completed | A voice call finishes, with duration, outcome, and summary. |
email.message.delivered | An email is accepted by the recipient mail server. |
email.message.opened | A recipient opens an email. |
email.message.clicked | A recipient clicks a link. data.url is the destination. |
email.message.bounced | An email bounces. The address is added to your suppression list. |
email.message.complained | A recipient marks an email as spam. |
shopify.order.created | A new order is placed in your connected store. |
shopify.order.fulfilled | An order is fulfilled, with tracking details when available. |
Registering an endpoint
/v1/webhooksRegister an endpoint and choose which events it receives. Returns the signing secret once.
curl -X POST https://api.synquic.com/api/v1/webhooks \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/webhooks/slide", "events": ["contact.created", "whatsapp.message.received"], "description": "Production CRM sync" }'
{
"id": "whe_4f8a2c1e",
"url": "https://example.com/webhooks/slide",
"description": "Production CRM sync",
"events": ["contact.created", "whatsapp.message.received"],
"isActive": true,
"status": "ACTIVE",
"createdVia": "api",
"consecutiveFailures": 0,
"lastSuccessAt": null,
"createdAt": "2026-08-20T10:11:12.000Z",
"secret": "whsec_XQ8vN2pLm4kR7tY1wZ3aB6cD9eF0gH5j"
}secret appears in this response and in the rotate response, and nowhere else, ever. Store it before you close the connection. If you lose it, rotate to get a new one and update your receiver, since rotating invalidates the old secret immediately.The URL must be publicly reachable and use https. Private, loopback, and link-local addresses are rejected, and the destination is re-checked on every delivery, so a hostname that later resolves to an internal address stops being delivered to.
Managing endpoints
/v1/webhooks/eventsThe event catalogue, with a sample payload for each event.
/v1/webhooksList your endpoints, paginated.
/v1/webhooks/:idRetrieve one endpoint, including its current health.
/v1/webhooks/:idChange the URL, the subscribed events, or enable and disable the endpoint.
/v1/webhooks/:idUnsubscribe. Returns 204 with no body.
/v1/webhooks/:id/rotate-secretIssue a new signing secret. The old one stops working immediately.
/v1/webhooks/:id/testQueue a test delivery. Returns 202. Pass eventType to receive that event's sample payload instead of a generic ping.
/v1/webhooks/:id/deliveriesDelivery log for one endpoint, newest first. Filter with ?status=FAILED.
/v1/webhooks/deliveries/:deliveryIdOne delivery in full, including the exact payload that was sent.
/v1/webhooks/deliveries/:deliveryId/replayRe-send a delivery. Reuses the original event id, so your idempotency check still applies.
Connecting Zapier
Zapier and other REST Hooks tools drive the endpoints above directly. A typical trigger wires up like this:
| Zapier step | What it calls |
|---|---|
| Authentication | API key in an Authorization: Bearer header, with webhooks:read and webhooks:write. |
| Trigger event list | GET /v1/webhooks/events |
| Subscribe (Zap on) | POST /v1/webhooks with the Zap’s target URL and the chosen event. |
| Unsubscribe (Zap off) | DELETE /v1/webhooks/:id |
| Perform list (test) | POST /v1/webhooks/:id/test, or read the delivery log for a recent real event. |
Scopes Reference
All available scopes and the operations they unlock. Scopes are assigned per API key at creation time and cannot be changed without re-issuing the key.
POST /messages/send endpoint (see the Messages API section above) does not have its own scope. It reuses whichever <channel>:send scope below matches the channel(s) you pass in channel / fallbackChannels - there is nothing extra to request here.| Scope | Module | Allowed Operations |
|---|---|---|
| contacts:read | Contacts | GET /contacts |
| webhooks:read | Webhooks | GET /webhooks, GET /webhooks/events, GET /webhooks/:id, GET /webhooks/:id/deliveries |
| webhooks:write | Webhooks | POST /webhooks, PATCH /webhooks/:id, DELETE /webhooks/:id, rotate-secret, test, replay |
| email:send | POST /email/send | |
| email:templates:read | GET /email/templates, GET /email/templates/:id | |
| email:templates:write | POST /email/templates, PUT /email/templates/:id | |
| email:contacts:read | GET /email/contacts | |
| email:contacts:write | POST /email/contacts | |
| email:campaigns:read | GET /email/campaigns | |
| instagram:messages:read | GET /instagram/profile, GET /instagram/conversations | |
| instagram:messages:send | POST /instagram/messages | |
| instagram:insights:read | GET /instagram/insights | |
| whatsapp:send | POST /whatsapp/send-template | |
| whatsapp:logs:read | GET /whatsapp/logs | |
| whatsapp:templates:read | GET /whatsapp/templates | |
| whatsapp:conversations:read | GET /whatsapp/conversations/by-phone | |
| whatsapp:campaigns:read | GET /whatsapp/campaigns, GET /whatsapp/campaigns/:id, campaign analytics | |
| whatsapp:campaigns:write | POST /whatsapp/campaigns, launch, cancel | |
| sms:send | SMS | POST /sms/send |
| sms:templates:read | SMS | GET /sms/templates |
| sms:senders:read | SMS | GET /sms/senders |
| sms:campaigns:read | SMS | GET /sms/campaigns, GET /sms/campaigns/:id |
| sms:campaigns:write | SMS | POST /sms/campaigns, launch, cancel |
| sms:logs:read | SMS | GET /sms/logs, GET /sms/stats |
| rcs:send | RCS | POST /rcs/send |
| rcs:bots:read | RCS | GET /rcs/bots |
| rcs:templates:read | RCS | GET /rcs/templates |
| rcs:campaigns:read | RCS | GET /rcs/campaigns |
| rcs:campaigns:write | RCS | POST /rcs/campaigns |
| rcs:logs:read | RCS | GET /rcs/logs, GET /rcs/stats |
| otp:send | OTP | POST /otp/send, POST /otp/retry |
| otp:verify | OTP | POST /otp/verify, POST /otp/verify-token |
| otp:logs:read | OTP | GET /otp/logs |
| otp:analytics:read | OTP | GET /otp/analytics |
| shopify:products:read | Shopify | GET /shopify/products/*, GET /shopify/collections/* |
| shopify:orders:read | Shopify | GET /shopify/orders/status, GET /shopify/customers/orders |
| shopify:discounts:read | Shopify | GET /shopify/discounts/validate |
| voice:agents:read | Voice | GET /voice/agents, GET /voice/agents/:id |
| voice:calls:read | Voice | GET /voice/calls, GET /voice/calls/:id, recordings, analytics |
| voice:calls:write | Voice | POST /voice/calls/outbound |
Ready to integrate?
Create your API key from the dashboard and start building in minutes.