ZupiChat API
Send WhatsApp template messages, manage contacts and receive delivery events from your own application. ZupiChat is operated by Codelith Lab Private Limited and is used by other businesses to run WhatsApp on their own WhatsApp Business Accounts.
Getting started
The API is REST over HTTPS. Requests and responses are JSON. Every response carries the same envelope, so a client can branch on one field:
{ "success": true, "message": "OK", "data": { } }
Base URL
https://zupichat.com/api/v1
Three steps to your first message
- Create a ZupiChat account and connect your WhatsApp Business number.
- Open Developer in the dashboard and generate an API key.
- Get one of your approved templates approved by Meta, then call
POST /messages/template.
Platform model
ZupiChat is software that other businesses run their own WhatsApp on. It is operated by Codelith Lab Private Limited (Pune, India). Every customer brings their own WhatsApp Business Account — ZupiChat does not resell numbers and does not message on its own behalf through a customer's account.
What belongs to whom
| Owned by the business | Provided by ZupiChat |
|---|---|
| The WhatsApp Business Account (WABA) and its ID | The dashboard, inbox, chatbot builder and campaign tooling |
| The phone number and its display name | This REST API, the webhook delivery layer and the API keys |
| Message templates, submitted under their own WABA | Template drafting, submission and approval tracking |
| The conversation history and contact list | Storage, search, export and the delivery-status pipeline |
Every account is its own tenant
- API keys are issued per account and scoped to it. A key can only read and write that account's contacts, templates and messages — there is no cross-account endpoint in this API.
- Webhooks are registered per account and fire only for that account's events.
- Each connected number carries its own Meta credentials, so one customer's token is never used to act for another.
- Revoking a key or disconnecting a number stops ZupiChat's access to that account immediately. The WABA and the number stay with the business.
In Meta's terms: the business is the WhatsApp Business Account owner and ZupiChat is the technology provider whose software they use to operate it. Billing for conversations is Meta's, charged to the account that owns the number.
Authentication
Send your key with every request. Three forms are accepted, so the API fits whatever your HTTP client makes easy:
Authorization: Bearer zc_live_xxxxxxxxxxxxxxxx
X-API-Key: zc_live_xxxxxxxxxxxxxxxx
?apiKey=zc_live_xxxxxxxxxxxxxxxx
Only a SHA-256 hash of the key is stored, so the key itself is shown once at creation and can never be read back. Revoke a key from the dashboard and it stops working immediately — other keys keep going.
Test mode and the sandbox number
Keys come in two kinds. A zc_live_ key sends real WhatsApp messages from
your own number. A zc_test_ key answers every endpoint with realistic
data and never reaches Meta — nothing is billed, and your contacts, campaigns
and reports stay untouched. Build the whole integration before your number is even
connected.
Try it right now
This key is public on purpose. It only ever simulates, so nothing can go wrong:
curl -X POST https://www.zupichat.com/api/v1/messages/template \
-H "Authorization: Bearer zc_demo_zupichat_public_sandbox" \
-H "Content-Type: application/json" \
-d '{ "destination": "919876543210",
"template_name": "sandbox_welcome",
"body_variables": ["Aditya"] }'
Test-mode responses carry two extra fields: mode and
delivery (simulated or real), plus a
note telling you what just happened.
Get it on a real phone
Send this on WhatsApp from the handset you want to test with:
That opens WhatsApp's 24-hour service window, so for the next 24 hours every test send to that number is actually delivered to it — no template approval, no WhatsApp Business Account of your own. Fair use: 20 real messages per number.
Signed in? Developer → Sandbox gives you your own join code, so joined phones and test sends stay private to your workspace.
| In test mode | What happens |
|---|---|
GET /me | Returns the sandbox identity and your join code. |
GET /templates | Three fixed templates: sandbox_welcome, order_confirmation_update, otp_login_code. |
POST /messages/template | Simulated, unless the destination has joined the sandbox. |
GET /messages/{id} | Ages sent → delivered → read, so polling code has something to poll. |
GET|POST /contacts | Fixture contacts. Nothing is written to your account. |
| Webhooks | Fire as usual with "mode": "test" in the payload. |
Account
Confirms the key works and tells you which WhatsApp number it is bound to. Useful as a health check after a customer pastes their key into your product.
curl https://zupichat.com/api/v1/me \
-H "Authorization: Bearer zc_live_xxxxxxxxxxxxxxxx"
{
"success": true,
"message": "OK",
"data": {
"account": { "id": 13, "name": "Aditya Sonawane", "email": "owner@example.com" },
"whatsapp": { "connected": true, "display_name": "Zupi Chat",
"phone_number": "918329608019", "phone_number_id": "1164348036759819" }
}
}
Templates
Lists the message templates on the connected WhatsApp Business Account with their approval status, so you can show your users only what can actually be sent.
curl https://zupichat.com/api/v1/templates \
-H "Authorization: Bearer zc_live_xxxxxxxxxxxxxxxx"
Send a template message
Template messages are the only way to start a conversation outside WhatsApp's 24-hour window, which is why this is the endpoint most integrations need.
| Field | Type | Notes |
|---|---|---|
destination required | string | Recipient in international format, e.g. 919876543210. |
template_name | string | Name of an approved template. Use this or template_id. |
template_id | integer | ZupiChat's own id for the template. |
body_variables | array | Values for {{1}}, {{2}}… in order. |
header_variables | array | Values for a variable in the header, if the template has one. |
name | string | Contact name to save when this number is new to the account. |
campaign_name | string | Label the send so it can be grouped in reports. |
curl -X POST https://zupichat.com/api/v1/messages/template \
-H "Authorization: Bearer zc_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"destination": "919876543210",
"template_name": "order_confirmation_update",
"name": "Aditya",
"body_variables": ["Aditya", "#1042"]
}'
Message status
Returns what WhatsApp last reported for that message — sent, delivered, read or failed. Poll it if you are not using webhooks.
curl https://zupichat.com/api/v1/messages/2340 \
-H "Authorization: Bearer zc_live_xxxxxxxxxxxxxxxx"
Contacts
Read the contact book, or push a lead into it the moment it appears in your own system.
| Field | Type | Notes |
|---|---|---|
mobile required | string | International format, e.g. 919876543210. |
firstname | string | Up to 60 characters. |
lastname | string | Up to 60 characters. |
note | string | Free text kept with the contact, up to 500 characters. |
curl -X POST https://zupichat.com/api/v1/contacts \
-H "Authorization: Bearer zc_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "mobile": "919876543210", "firstname": "Aditya", "note": "Came from the pricing page" }'
Webhooks
Register an endpoint in Developer → Webhooks and ZupiChat posts events to it as they happen, so you do not have to poll.
| Event | Fires when |
|---|---|
message.received | A customer sends a message to your WhatsApp number. |
message.sent | You send a message, from the panel or through this API. |
message.status | WhatsApp reports a message as sent, delivered, read or failed. |
contact.created | A number reaches your account for the first time. |
Verifying the signature
Every delivery carries an HMAC of the raw body, signed with the secret shown when you created the webhook. Compare it before trusting the payload:
X-ZupiChat-Event: message.status
X-ZupiChat-Delivery: 9f2c1b7a4e5d6c30
X-ZupiChat-Signature: sha256=<hmac_sha256(raw_body, your_webhook_secret)>
Answer 2xx within 3 seconds. Anything slower is logged as a failed
delivery and shows up in Developer → Request Log.
{
"event": "message.status",
"created_at": "2026-08-22T11:08:04+05:30",
"data": { "message_id": 2340, "whatsapp_message_id": "wamid.HBgMOTE4...",
"conversation_id": 74, "status": "delivered" }
}
OpenAPI & Postman
The whole contract is published as a machine-readable spec, generated from the same source the endpoints run on — so it cannot drift out of date. Import it and your client, types and tests come out of it.
Use it
In Postman: Import → Link and paste either URL. Set the collection
variable apiKey once and every request is authenticated.
Prefer to read it in a browser?
Open it in Swagger Editor.
# generate a typed client in any language
npx @openapitools/openapi-generator-cli generate \
-i https://www.zupichat.com/openapi.json -g typescript-axios -o ./zupichat
Errors
Failures use standard HTTP codes and the same envelope with "success": false.
| Code | Meaning |
|---|---|
401 | No API key was sent, or the key is invalid or revoked. |
403 | The account behind the key is inactive. |
404 | The template, message or contact does not belong to this account. |
429 | Too many calls on the public sandbox key from one IP. Your own key has no such cap. |
422 | A field is missing or malformed — the message says which. |
Limits and fair use
ZupiChat does not add its own throughput cap. What governs your volume is WhatsApp's own messaging tier for the connected number, which Meta raises as your quality rating and traffic grow.
Two rules are enforced on every send, because they are what keeps a number healthy: a contact who has opted out is never messaged, and a template can only be used once Meta has approved it.
Versioning
The version lives in the path. v1 is stable: fields get added, never
removed or renamed, so an integration written today keeps working. Anything that
would break a client ships under a new prefix instead, and the OpenAPI document
above is generated from the running code, so it always matches what the API does.
Questions, or need an endpoint that is not here yet? Write to support@zupichat.com — the API grows with what integrators actually ask for.