Documentation

Everything you need to receive and send WhatsApp messages with Kobry.

Quickstart

Kobry connects the number you already use on the WhatsApp Business app to the Cloud API (Meta calls this Coexistence). You keep chatting from your phone, and your automations get every message through a webhook and can reply through a simple REST API.

  1. Create your account and choose a plan (Vodafone Cash in Egypt, or Visa/Mastercard from any country).
  2. Open Connections → Connect a number and finish Meta’s signup with the number on your WhatsApp Business app.
  3. Paste your webhook URL (for example your n8n Webhook node) and press Send test event.
  4. Create an API key in API keys and send your first message.

Your WhatsApp Business app must be version 2.24.17 or newer. Keep the phone app — you keep using it as before.

Receiving messages (webhooks)

Every event Meta sends for your number — new messages, delivery and read statuses, messages you send from the phone app (smb_message_echoes), template updates — is forwarded to your URL as an HTTPS POST. The body is Meta’s original JSON, unchanged, so Meta’s documentation applies as-is.

Example: a new text message
{
  "object": "whatsapp_business_account",
  "entry": [{
    "id": "102290129340398",
    "changes": [{
      "field": "messages",
      "value": {
        "messaging_product": "whatsapp",
        "metadata": { "display_phone_number": "201000000000", "phone_number_id": "106540352242922" },
        "contacts": [{ "profile": { "name": "Mona" }, "wa_id": "201012345678" }],
        "messages": [{
          "from": "201012345678",
          "id": "wamid.HBgM...",
          "timestamp": "1700000000",
          "type": "text",
          "text": { "body": "Is the blue hoodie available?" }
        }]
      }
    }]
  }]
}
  • Answer with any 2xx status within 5 seconds. Do slow work after you respond.
  • If your server is down or slow, we retry the event for up to 24 hours (after 10s, 1m, 5m, 15m, 30m, then hourly). It shows as Retrying in Webhook logs until it is delivered. Because of retries you may rarely see the same event twice — use the message id to ignore duplicates.
  • Your URL must be HTTPS on port 443, must not redirect, and must point to a public address.
  • We never store message content — except an event your server didn't accept yet: it is held encrypted while we retry, deleted the moment it's delivered, and never kept longer than 24 hours. Only metadata (event type, result, timing) is kept for 7 days in Webhook logs.

Verifying the signature

Every request we send carries X-Kobry-Signature: sha256=<hex> — an HMAC-SHA256 of the raw request body using your connection’s signing secret (Connections → Signing secret). Check it so nobody else can fake events to your URL.

Node.js (Express)
const crypto = require('crypto');

app.post('/whatsapp', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.KOBRY_SECRET)
    .update(req.body) // raw bytes, not re-serialized JSON
    .digest('hex');
  const received = req.get('X-Kobry-Signature') || '';
  const ok = expected.length === received.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
  if (!ok) return res.sendStatus(401);

  res.sendStatus(200); // answer fast…
  handle(JSON.parse(req.body)); // …then do the work
});
Python
import hmac, hashlib

def is_valid(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header or "")

Sending messages

Send with POST https://kobry.app/v1/messages and your API key in the Authorization header. The body is Meta’s message object (so every message type Meta supports works), plus an optional connection_id when your workspace has more than one number.

Text message (only within 24 hours of the customer’s last message)
curl -X POST https://kobry.app/v1/messages \
  -H "Authorization: Bearer kob_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "201012345678",
    "type": "text",
    "text": { "body": "Yes, size L is in stock ✅" }
  }'
Template message (to start a conversation)
curl -X POST https://kobry.app/v1/messages \
  -H "Authorization: Bearer kob_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "201012345678",
    "type": "template",
    "template": {
      "name": "order_ready",
      "language": { "code": "ar" },
      "components": [{
        "type": "body",
        "parameters": [{ "type": "text", "text": "Mona" }, { "type": "text", "text": "A-1001" }]
      }]
    }
  }'
Response
{
  "messaging_product": "whatsapp",
  "contacts": [{ "input": "201012345678", "wa_id": "201012345678" }],
  "messages": [{ "id": "wamid.HBgM..." }]
}

WhatsApp only allows free-form messages within 24 hours of the customer’s last message. Outside that window, send an approved template.

GET /v1/connections lists your numbers and their ids. A key can be limited to one number when you create it.

Templates

Create and manage templates from the dashboard (Templates) or the API. Meta reviews every new template, usually within minutes.

RequestWhat it does
GET /v1/templatesList templates with their status (APPROVED, PENDING, REJECTED…).
POST /v1/templatesCreate a template — same body as Meta (name, language, category, components).
DELETE /v1/templates/{name}Delete a template by name.

Add ?connection_id=… (or "connection_id" in the body) when your workspace has several numbers.

n8n guide

Receive WhatsApp messages in n8n and reply — no code needed for the basics.

  1. Webhook node — HTTP Method POST, choose a path, Respond: Immediately. Copy the Production URL.
  2. In Kobry: Connections → paste that URL → Save URL → Send test event. Activate your workflow first, or use the Test URL while testing.
  3. Read the message: the text is at {{ $json.body.entry[0].changes[0].value.messages[0].text.body }} and the sender at …messages[0].from.
  4. IF node — continue only when messages exists (status updates arrive on the same webhook).
  5. HTTP Request node to reply — Method POST, URL https://kobry.app/v1/messages, Authentication → Generic Credential → Header Auth (Name Authorization, Value Bearer kob_live_…), Body JSON as in “Sending messages”.

To verify the signature in n8n, turn on Raw Body in the Webhook node options and add a Code node before your logic:

n8n Code node — verify signature
const crypto = require('crypto');
const item = $input.first();
const raw = Buffer.from(item.binary.data.data, 'base64');
const expected = 'sha256=' + crypto
  .createHmac('sha256', 'PASTE_YOUR_SIGNING_SECRET')
  .update(raw)
  .digest('hex');
const received = item.json.headers['x-kobry-signature'] || '';
if (expected !== received) throw new Error('Invalid signature');
return [{ json: JSON.parse(raw.toString('utf8')) }];

Self-hosted n8n blocks require('crypto') unless the environment variable NODE_FUNCTION_ALLOW_BUILTIN=crypto is set.

Errors and limits

Every error has the same shape: { "error": { "code": "…", "message": "…" } }.

StatuscodeMeaning
400meta_rejectedMeta refused the request — the message explains why.
401unauthorizedMissing, wrong or revoked API key.
402plan_requiredYour workspace has no active plan.
403forbidden_connectionThis key is limited to another number.
404connection_not_foundNo such number in your workspace.
409connection_not_readyThe number is paused or not connected yet.
413payload_too_largeBody over 64 KB.
422invalid_request / connection_requiredFix the body, or pass connection_id.
429rate_limitedOver 600 requests per minute for this key — see Retry-After.
502meta_unavailableMeta had a problem. Try again shortly.

Good to know (Coexistence)

  • Group chats don’t sync to the API.
  • After connecting, disappearing messages, view once, live location and broadcast lists are turned off on the app.
  • Coexistence numbers send up to about 20 messages per second.
  • Open the WhatsApp Business app regularly (at least every two weeks) — Meta may disconnect inactive numbers.
  • Meta’s messaging fees are paid by you directly to Meta. We add no markup.