Dakiya

Dakiya API

Connect your CRM, website or billing software to WhatsApp: send approved templates, start broadcasts, keep contacts in sync, and receive customer replies and read receipts as they happen. All requests and responses are JSON over HTTPS.

Quick start

  1. In the app open Developers and create an API key. It is shown once; store it on your server, never in a browser or mobile app.
  2. Send your first template:
curl -X POST "https://dakiya.smartaccounts.in/api/v1/messages/template" \
  -H "Authorization: Bearer dk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "919876543210",
    "template": "order_update",
    "language": "en",
    "body": ["Meera", "#4521"]
  }'
{ "ok": true, "message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS...", "to": "919876543210", "status": "sent", "charged": 0.135, "balance": 1842.615 }

charged is what was taken from the prepaid message balance and balance is what is left. Both are absent on accounts that are not charged per message.

  1. Add a webhook address under Developers, Webhooks to hear when it is delivered, read or answered.

Authentication

Base address: https://dakiya.smartaccounts.in/api/v1. If your server cannot reach it, the same API also answers at https://dakiya.smartaccounts.in/api.php/v1.

Send the key in every request: Authorization: Bearer dk_... (or the header X-API-Key). A key belongs to one workspace and can only see that workspace's data. Delete a key under Developers to revoke it at once.

Phone numbers are digits with country code and no plus sign in responses (919876543210). In requests, +91 98765 43210 and 10-digit numbers for the workspace's default country are accepted too. Times are ISO 8601 in UTC.

Errors

A failed call returns a matching HTTP status and this shape. Use code in your program and show message to people.

{ "ok": false, "error": { "code": "outside_24h_window", "message": "The customer has not written in the last 24 hours, so WhatsApp only allows an approved template." } }
StatusCodeMeaning
400bad_jsonThe body is not valid JSON.
401unauthorizedMissing, wrong or deleted key.
402insufficient_balanceThe prepaid message balance does not cover this message. Add money under Plan and billing; nothing was sent.
402account_expired, account_suspended, account_pending, quota_exceeded, contact_limitThe plan does not allow this right now. Renew or upgrade under Plan and billing.
403plan_without_apiThe workspace's plan has no API access.
404not_found, template_not_found, tag_not_found, unknown_routeThe thing you named does not exist in this workspace.
409outside_24h_window, unsubscribed, whatsapp_not_connected, not_cancellableThe request is valid but not allowed in the current state.
422invalid_phone, missing_variable, missing_template, invalid_url, country_not_priced, ...Something in the request is missing or malformed. The message says what.
429rate_limitedToo many calls this minute. Wait for the seconds in Retry-After.
502whatsapp_rejectedWhatsApp refused the message. The message carries WhatsApp's reason.

Message charges

Each message is charged from the workspace's prepaid balance when WhatsApp accepts it: template messages by their category (marketing, utility, authentication), free-form messages as service replies. If WhatsApp later reports the message as failed, the charge is returned and you receive a message.failed webhook. GET /me returns the current balance and prices. A broadcast pauses when the balance runs out and continues by itself after a top-up.

Rate limits

Each key may make a set number of calls per minute, depending on the plan. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. To message many people at once, use one broadcast call instead of thousands of single sends: it is one API call and is paced for you.

Safe retries

Networks fail. To retry a POST without risking a duplicate WhatsApp message, send a unique Idempotency-Key header (for example your own order or invoice id). Repeating the call with the same key within 24 hours returns the first answer and sends nothing new; the reply then carries Idempotent-Replay: true.

Sending

POST/messages/template

Sends one approved template message. Works at any time; this is how a conversation is started. The contact is created if it does not exist.

FieldTypeNotes
tostring, requiredRecipient's WhatsApp number.
templatestring, requiredTemplate name, as listed by GET /templates.
languagestringFor example en, hi, gu. Needed only when the template exists in several languages.
bodyarray or objectValues for the message variables, in order: ["Meera","#4521"], or by name: {"1":"Meera","2":"#4521"}.
headerarray or objectValue for a text header's variable, if it has one.
header_media_urlstringPublic https link to the image, video or PDF, for templates with a media header. header_media_name sets the file name of a document.
buttonsobjectValues for dynamic buttons, by button position: {"0":"track/4521"} for a link ending or a coupon code.
name, email, tags, fieldsOptional contact details to save at the same time.
ignore_unsubscribedbooleanSend even if the contact opted out. Only for messages they still expect, such as an order or payment update.

POST/messages/text

Sends free text. WhatsApp allows this only within 24 hours of the customer's last message; otherwise the call fails with outside_24h_window.

curl -X POST "https://dakiya.smartaccounts.in/api/v1/messages/text" -H "Authorization: Bearer dk_your_key" -H "Content-Type: application/json" \
  -d '{"to":"919876543210","text":"Your order is packed and leaves today."}'

Two more calls follow the same 24-hour rule:

  • POST/messages/media with to, type (image, video, audio or document), url (public https link), and optional caption and filename.
  • POST/messages/buttons with to, text and buttons: one to three short labels, for example ["Yes","No"]. The label the customer taps arrives as a normal incoming message.

GET/messages/{id}

Returns the latest known status of a message you sent: sent, delivered, read or failed (with error). Prefer webhooks over polling this.

POST/broadcasts

Sends one template to many people in a single call. The service queues the messages, paces them within WhatsApp's limits, skips people who unsubscribed, and reports progress.

curl -X POST "https://dakiya.smartaccounts.in/api/v1/broadcasts" -H "Authorization: Bearer dk_your_key" -H "Content-Type: application/json" \
  -d '{
    "name": "Payment reminders, October",
    "template": "payment_reminder",
    "body": ["{name|Customer}", "{invoice_no}", "{amount}", "15 Oct"],
    "to": { "contacts": [
      { "phone": "919876543210", "name": "Meera Shah",  "fields": { "invoice_no": "INV-101", "amount": "4,500" } },
      { "phone": "919812345678", "name": "Rohan Patel", "fields": { "invoice_no": "INV-102", "amount": "12,000" } }
    ] },
    "schedule_at": "2026-10-20T10:30:00+05:30"
  }'
  • to is one of: {"contacts":[{phone, name, email, fields}]} or {"phones":[...]} (up to 10,000 per call), {"tags":["VIP"]}, or {"all":true}.
  • Template values may contain placeholders filled per contact: {name}, {first_name}, {phone}, {email}, or any custom field such as {invoice_no}. Add a fallback after a bar: {first_name|there}. A contact whose value is empty and has no fallback is skipped, not sent a broken message.
  • Leave out schedule_at to start immediately.
{ "ok": true, "broadcast_id": 42, "status": "scheduled", "recipients": 2, "invalid_numbers": 0, "scheduled_at": "2026-10-20T05:00:00+00:00" }

GET/broadcasts/{id} returns the status and counts (recipients, waiting, sent, delivered, read, replied, failed, skipped). GET/broadcasts lists the latest 50. POST/broadcasts/{id}/cancel stops one that is scheduled, running or paused.

Data

POST/contacts

Creates a contact or updates the one with the same number.

curl -X POST "https://dakiya.smartaccounts.in/api/v1/contacts" -H "Authorization: Bearer dk_your_key" -H "Content-Type: application/json" \
  -d '{"phone":"919876543210","name":"Meera Shah","email":"meera@example.com",
       "tags":["Lead","Gandhinagar"],"fields":{"city":"Gandhinagar","plan":"Gold"},"subscribed":true}'

Existing names and emails are kept unless you pass "overwrite": true. Tags are added; use remove_tags to take some away. New field names appear in the app once you add a custom field with the same name.

  • GET/contacts?search=&tag=&subscribed=&page=&limit= lists contacts, newest first (up to 200 per page).
  • GET/contacts/{phone} returns one contact.
  • DELETE/contacts/{phone} removes it.

GET/templates

Lists the workspace's templates with their status and the variables each one needs, so your software can build the right form.

GET/conversations/{phone}/messages

Returns the latest messages exchanged with one customer (up to 200, oldest first) and whether the 24-hour window is open. Incoming files have a media_download address that needs the same API key.

GET/me

Returns the workspace, the connected WhatsApp number and its quality, the plan, whether sending is currently allowed, this month's usage, and the prepaid balance with the price per message type. Useful as a connection test.

Webhooks

A webhook is an https address on your server that we call when something happens. Add addresses under Developers, Webhooks, or through the API:

curl -X POST "https://dakiya.smartaccounts.in/api/v1/webhooks" -H "Authorization: Bearer dk_your_key" -H "Content-Type: application/json" \
  -d '{"url":"https://crm.example.com/hooks/whatsapp","events":["message.received","message.read","message.failed"]}'

The answer includes a secret used to sign every delivery. Leave out events to receive everything. GET/webhooks lists addresses; DELETE/webhooks/{id} removes one.

Each delivery is a POST with a JSON body like this:

{
  "id": "evt_5f2a9c0e7b1d4a66c3e8f0a1",
  "event": "message.received",
  "created_at": "2026-10-09T06:12:44+00:00",
  "workspace_id": 7,
  "data": {
    "message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAEhgU...",
    "from": "919876543210",
    "name": "Meera Shah",
    "type": "text",
    "text": "Is this available in blue?",
    "media": null,
    "reply_to_broadcast_id": 42,
    "timestamp": "2026-10-09T06:12:43+00:00"
  }
}
  • Answer with any 2xx status within 6 seconds. Do slow work after answering.
  • If your server is down or answers with an error, we retry after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then stop. Deliveries and their results are listed under Developers.
  • The same event can occasionally arrive twice. Use id to ignore repeats.
  • Events can arrive out of order (a read before a delivered). Use timestamp.

Verify the signature

Every delivery carries X-Dakiya-Signature: sha256=..., the HMAC-SHA256 of the raw request body using the address's secret. Check it before trusting the content.

<?php  // PHP
$body = file_get_contents("php://input");
$sign = "sha256=" . hash_hmac("sha256", $body, "whsec_your_secret");
if (!hash_equals($sign, $_SERVER["HTTP_X_DAKIYA_SIGNATURE"] ?? "")) { http_response_code(401); exit; }
$event = json_decode($body, true);
http_response_code(200);
// Node.js (Express): use the raw body, not the parsed one
const crypto = require("crypto");
app.post("/hooks/whatsapp", express.raw({ type: "application/json" }), (req, res) => {
  const sign = "sha256=" + crypto.createHmac("sha256", "whsec_your_secret").update(req.body).digest("hex");
  const got = req.get("X-Dakiya-Signature") || "";
  if (sign.length !== got.length || !crypto.timingSafeEqual(Buffer.from(sign), Buffer.from(got))) return res.sendStatus(401);
  const event = JSON.parse(req.body);
  res.sendStatus(200);
});

Event reference

EventWhenFields in data
message.receivedA customer sent a message or tapped a button.message_id, from, name, type, text, media (id, mime_type, filename, download), reply_to_broadcast_id, timestamp
message.delivered
message.read
message.failed
A message you sent (single or broadcast) changed status.message_id, to, status, timestamp, broadcast_id, error, error_code
contact.unsubscribed
contact.subscribed
A contact sent a stop or start word.phone, name, keyword
template.statusWhatsApp approved, rejected, paused or disabled a template.name, language, status, reason
broadcast.completedA broadcast finished sending.broadcast_id, name, recipients, sent, failed, skipped

Status events for a large broadcast can be many thousands. Subscribe to them only if your CRM stores per-message status; otherwise use broadcast.completed and GET /broadcasts/{id}.