Developer Documentation

Riffre API Reference

Everything you need to send WhatsApp text, template, media and interactive messages from your own systems.

Bot Webhooks

Make a bot template fetch its reply from your own server at send time - for order tracking, live catalogs, booking confirmations, or anything that needs current data instead of fixed text.

Last updated 08 Aug 2026

What it is

A bot template normally sends fixed, stored content when its trigger matches an incoming message. Webhook mode replaces that fixed content with a live call to your own HTTPS endpoint - Riffre sends you context about the trigger and the customer, your endpoint decides what to reply with, and Riffre delivers it. The customer is billed and the message queued through the same flow as every other message.

Use webhook mode when the right reply depends on data that changes per customer or per moment - an order's current status, today's in-stock catalog, a booking confirmation you just created. Use a static template when the reply is always the same text.

There is no one-size-fits-all payload here: an order-tracking integration and a catalog integration return completely different content. The contract below is deliberately generic - your endpoint returns the same shape you'd use to build a bot template by hand in the dashboard (a header, a body, an optional footer, and optionally buttons or a list) - so whatever you can build in the Bot section's template form, you can also return dynamically.

Setting it up

In WhatsApp Accounts → a number → Bot → Create bot template, choose External webhook as the reply mode and enter your HTTPS endpoint. Riffre generates a signing secret the first time you save and shows it to you once - store it like any other credential. The template's trigger (or the buttons/list options on other templates that route to it) still works exactly as documented in the Bot section; only *what gets sent* changes.

Buttons and list options are only turned into new triggers automatically for static templates, where the option set is fixed at save time. A webhook's buttons/list rows are generated live and are not registered as triggers - if a reply option should itself be tappable and lead somewhere, have your endpoint tell the customer what to type next, or configure that phrase as its own trigger separately.

Request - what Riffre sends you

POST to your configured URL, Content-Type: application/json, called once per matched trigger:

{
  "event": "bot.trigger.matched",
  "request_id": "5b6f1a2e-9c3a-4b7e-8b0a-2b6b6a2c9e10",
  "trigger": "track shipment",
  "bot_template": { "id": 42, "name": "Track shipment" },
  "message": {
    "id": "wamid.HBgLOTE5ODc2NTQzMjEV",
    "type": "text",
    "text": "track shipment"
  },
  "contact": { "wa_id": "919876543210", "name": "Rohit Sharma" },
  "phone": {
    "id": "1143516278855102",
    "display_phone_number": "+91 90000 00001",
    "waba_name": "Acme Store"
  },
  "timestamp": "2026-08-08T11:32:00+00:00"
}

message.text is the customer's raw inbound text (or the button/list title they tapped) - useful if you want to parse extra detail out of it yourself, but most integrations only need contact.wa_id to look up the right customer/order/booking in your own system, the same way your website already recognizes a returning customer.

Headers

HeaderPurpose
X-Riffre-Signaturesha256=<hex hmac> - HMAC-SHA256 of the raw request body, keyed with your signing secret
X-Riffre-Request-IdMatches request_id in the body; use it to deduplicate retried calls

Verifying the signature

$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, $yourSigningSecret);
if (!hash_equals($expected, $_SERVER['HTTP_X_RIFFRE_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', yourSigningSecret).update(rawBody).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-riffre-signature'] || ''));

Response - what your endpoint returns

Respond 200 OK with Content-Type: application/json within 8 seconds. All fields except body are optional.

FieldTypeLimit
header_textstring60 characters
bodystringrequired, 1024 characters
footerstring60 characters
interactive_type"none""buttons""list"defaults to "none"
buttonsarray of { "title": string }up to 3, 20 characters each - only used when interactive_type is "buttons"
list_button_textstring20 characters - only used when interactive_type is "list"
list_sectionsarray of { "title": string, "rows": [{ "title": string, "description": string }] }up to 10 rows total, 24/72 characters - only used when interactive_type is "list"

If your endpoint times out, errors, or returns invalid JSON, Riffre sends the template's configured fallback message instead (a plain, fixed line of text you set when creating the template). If no fallback is set, the customer simply receives no reply for that trigger and the failure is logged - configure a fallback for anything customer-facing.

Example 1 - Order tracking (plain text)

Trigger: track shipment (or route to it from a button on your main menu template). Your endpoint looks up the customer's most recent order by contact.wa_id and replies with its status.

{
  "header_text": "Shipment status",
  "body": "Your order ORD-48213 is out for delivery and should arrive today by 6 PM.",
  "footer": "Reply "track shipment" anytime for an update"
}

Example 2 - Category catalog (interactive list)

Trigger: browse catalog. Your endpoint returns whatever categories are currently in stock, built fresh on every call - no need to update a static template when your catalog changes.

{
  "body": "Here's what we have in stock today:",
  "interactive_type": "list",
  "list_button_text": "View categories",
  "list_sections": [
    {
      "title": "Electronics",
      "rows": [
        { "title": "Phones", "description": "12 models in stock" },
        { "title": "Laptops", "description": "Gaming & office" }
      ]
    },
    {
      "title": "Fashion",
      "rows": [
        { "title": "Men", "description": "" },
        { "title": "Women", "description": "" }
      ]
    }
  ]
}

Example 3 - Appointment booking acknowledgement (interactive buttons)

Trigger: book appointment (typically the last step after a short conversation your own system already tracked). Your endpoint creates the booking, then confirms it and offers next actions.

{
  "body": "Your appointment is confirmed for Aug 12, 3:00 PM with Dr. Mehta.",
  "footer": "Booking ref: APT-9042",
  "interactive_type": "buttons",
  "buttons": [
    { "title": "Reschedule" },
    { "title": "Cancel" }
  ]
}

Errors and reliability

  • Riffre calls your endpoint synchronously while processing the customer's incoming message, with an 8-second timeout and a 4-second connect timeout. Keep your handler fast - do the lookup, don't do heavy work inline.
  • A non-2xx response, a connection error, a timeout, or a body that isn't valid JSON all fall back to the template's fallback message (or silence, if none is set).
  • Trigger matching itself happens before your endpoint is called and is unaffected by your response - it is always an exact, case-insensitive match against the configured trigger text (see the Bot section documentation for how triggers are created).