Connect your tools to Zinvee Inbox
Let another system (your CRM, a website form, an automation tool) add and tag contacts, move them along the pipeline, read conversations and send WhatsApp messages and templates, and hear the moment a message arrives.
Base URL https://api.dm.diginetacademy.in/api/v1/public. Every endpoint is in the API reference.
Quick start
- In Zinvee Inbox, open Settings → API & webhooks and create a key with only the scopes the tool needs. The key is shown once.
- Check it works:
export INBOX_KEY="ik_live_…" curl https://api.dm.diginetacademy.in/api/v1/public/me \ -H "Authorization: Bearer $INBOX_KEY" - Add a contact with tags, or update the one with that phone:
curl -X POST https://api.dm.diginetacademy.in/api/v1/public/contacts \ -H "Authorization: Bearer $INBOX_KEY" \ -H "Content-Type: application/json" \ -d '{"phone": "919876543210", "name": "Asha Menon", "tags": ["hot-lead"]}' - Send an approved template, which WhatsApp delivers at any time:
curl -X POST https://api.dm.diginetacademy.in/api/v1/public/messages \ -H "Authorization: Bearer $INBOX_KEY" \ -H "Content-Type: application/json" \ -d '{"phone": "919876543210", "kind": "template", "template_name": "welcome_msg", "template_language": "en", "name_param": true}'
Keys and scopes
Send the key on every request as Authorization: Bearer ik_live_…. Keys are shown once and stored only as a SHA-256 hash, and they open only the public API. A key holds one or more scopes; a request outside them gets 403 forbidden naming the scope it needed. GET /me needs no scope.
| Scope | Unlocks |
|---|---|
contacts:read | GET /contacts, GET /contacts/{phone}, GET /stages |
contacts:write | POST /contacts, POST /contacts/{phone}/tags, POST /contacts/{phone}/stage |
conversations:read | GET /conversations, GET /conversations/{phone}/messages |
messages:send | POST /messages |
campaigns:read | GET /campaigns, GET /campaigns/{id} |
flows:read | GET /flows |
Phones and bodies
- Bodies are JSON both ways.
- Phones are digits with the country code and nothing else:
919876543210. Anything you send is cleaned up the way the inbox does it, so+91 98765 43210is the same person. - Someone who has only written on Instagram or the website chat has no phone. They appear in
GET /contacts, but routes under/contacts/{phone}can’t reach them. - A text message goes only inside WhatsApp’s 24-hour customer-service window, which starts at the contact’s last message to you. Outside it, send a template.
Errors
Every error is {"error": "<a sentence>", "code": "<a word>"}, sometimes with a field or two more. Branch on code.
| Status | Code |
|---|---|
400 | invalid_* for a bad input: invalid_phone, invalid_body, invalid_tags, invalid_stage and so on. |
401 | unauthorized: no key, or not a valid one. |
403 | forbidden: the key lacks the scope, which the body names in scope. |
404 | not_found |
409 | window_closed: a text message outside WhatsApp’s 24-hour window. Send a template instead. |
429 | rate_limited, with retry_after and a Retry-After header. |
500 | server_error |
502 | send_failed: Meta refused the message. Carries meta_code, transient and message_id. |
Rate limit
Each key may make 300 requests a minute. Over that, the answer is 429 with a Retry-After header and retry_after in the body:
{"error": "This key may make 300 requests a minute; try again in 12s", "code": "rate_limited", "retry_after": 12}Webhooks
Under Settings → API & webhooks → Add endpoint, give a URL and pick events. You get a secret once; keep it on the receiving side. Each event is a POST to your URL:
Content-Type: application/json
X-Event: message.received
X-Delivery-Id: 1842
X-Signature: 3f0e… (hex HMAC-SHA256 of the raw body, keyed by the secret)
{"id": 1842, "event": "message.received", "created_at": "2026-11-14T08:00:00.412Z", "data": {…}}- Answer any 2xx within 10 seconds. Anything else (a 4xx, a 5xx, a timeout, a redirect) is retried: 5 attempts in all, starting 30 seconds later and backing off.
- 20 failed deliveries in a row switch the endpoint off; it says so under the endpoint, and switching it back on resets the count.
- Deliveries can arrive out of order and, rarely, twice. Use id to de-duplicate.
- The Test button sends
{"event": "ping"}whatever the endpoint subscribed to.
Check the signature over the raw body before trusting a delivery:
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.INBOX_WEBHOOK_SECRET; // from the endpoint dialog
const app = express();
// The body must be the raw bytes: parse it only after checking.
app.post("/hooks/inbox", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const given = String(req.header("x-signature") ?? "");
const ok = given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!ok) return res.status(401).end();
const delivery = JSON.parse(req.body.toString("utf8"));
// delivery.id, delivery.event, delivery.created_at, delivery.data
res.status(204).end(); // answer first, work after
});The events and what each carries are in the API reference.