Command Palette

Search for a command to run...

IntelliPortalDOCS

Start here

  • Overview
  • Quickstart
  • Test in the sandbox

Build

  • Authentication
  • Clients
  • Hosted onboarding
  • Webhooks

API reference

  • Messages
  • Templates
  • Usage
  • Status & observability

Reliability

  • WhatsApp limits
  • Errors & debugging

Resources

  • Changelog
  • OpenAPI 3.1
  • Postman collection
Developer support
IntelliPortalDOCS
Developer documentation/API v1

© 2026 Intelli Holdings Inc.

PrivacyTermsEmail support

Need a hand?

Share a request ID when you contact us so we can trace the call quickly.

Open developer support
  1. Docs
  2. Webhooks

Core concept

HMAC-SHA256

Webhooks

Receive inbound messages and delivery lifecycle events through one signed endpoint, with channel and client_ref included for deterministic routing.

Configure one HTTPS endpoint

Save the receiver URL and event selection in the dashboard's Webhooks section. Copy the generated signing secret into your application's secret manager. Webhook configuration is currently managed through the portal, not a public /v1/webhooks route.

Configure webhooks

Event types

Subscribe only to events your product handles. New fields can be added over time, so ignore unknown properties.

eventChannelMeaning
message.receivedWhatsApp, InstagramAn end user sent a message.
message.statusWhatsAppA sent message moved to sent, delivered, read, or failed.
message.reactionWhatsApp, InstagramAn end user added or removed a reaction.
message.echoInstagramThe connected professional account sent a message.
message.readInstagramA user read a message.
message.postbackInstagramA user activated a postback action.
template.statusWhatsAppMeta finished reviewing a message template. Carries template.status (APPROVED, REJECTED, PAUSED, …) and template.reason.
user.preferencesWhatsAppA user opted in or out of marketing messages.
webhook.testAllA signed diagnostic event from the dashboard.

Route the event envelope

Use event to select a handler, channel for the provider-specific body, and client_ref to find your customer. Provider data stays close to the source payload so you can preserve message fidelity.

WhatsApp · message.received
{
  "event": "message.received",
  "channel": "whatsapp",
  "timestamp": "2026-07-24T09:42:18.000Z",
  "client_ref": "customer_42",
  "phone_number": "+254711111111",
  "contacts": [
    {
      "profile": { "name": "Amina" },
      "wa_id": "254700000000"
    }
  ],
  "messages": [
    {
      "from": "254700000000",
      "id": "wamid.xxx",
      "type": "text",
      "text": { "body": "Is my order ready?" }
    }
  ]
}
Instagram · message.received
{
  "event": "message.received",
  "channel": "instagram",
  "timestamp": "2026-07-24T09:44:01.000Z",
  "client_ref": "customer_42_instagram",
  "contact": {
    "id": "17841400000000000",
    "username": "ama_mensah",
    "name": "Ama Mensah"
  },
  "messages": [
    {
      "id": "mid.xxx",
      "sender": {
        "id": "17841400000000000",
        "username": "ama_mensah",
        "name": "Ama Mensah"
      },
      "recipient": { "id": "17841499999999999" },
      "text": "Can you help?"
    }
  ]
}

Downloading media a customer sent

The two channels hand you inbound media differently.

WhatsApp gives an id only — messages[].image.id and friends. Fetching the file needs the client's Meta credentials, which you never hold, so request it from us: GET /v1/media/<media_id>?client_ref=<ref> with your API key (clients:read scope). It streams the bytes back with the real Content-Type, plus Content-Length and an X-Media-Sha256 you can verify against the webhook. Meta keeps inbound media for 30 days.

Instagram needs no call: the payload already carries a fetchable URL at attachments[].payload.url. It is short-lived, so download on receipt rather than when someone opens the conversation.

Instagram users arrive with their handle

Instagram identifies people by an Instagram-scoped ID, which is opaque and account-specific. We resolve it for you: every participant that isn't your client's own business account carries username and name, and the person the event is about is repeated at the top level as contact. Both are omitted when Instagram has no profile to return, so treat them as optional and keep keying your records on the ID.

Store provider message IDs

Use the message or status ID as your deduplication key. Automatic retries and manual redelivery can cause the same event to arrive more than once.

Verify the signature before parsing

Read X-Intelli-Signature, compute HMAC-SHA256 over the exact raw request bytes, prefix the hex digest with sha256=, and compare in constant time.

Signature verification
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyIntelliSignature(
  rawBody: Buffer,
  signature: string,
  secret: string,
) {
  const expected =
    "sha256=" +
    createHmac("sha256", secret).update(rawBody).digest("hex");

  const received = Buffer.from(signature);
  const calculated = Buffer.from(expected);

  return (
    received.length === calculated.length &&
    timingSafeEqual(received, calculated)
  );
}

Do not re-serialize JSON

Parsing and stringifying the body changes whitespace or key order and invalidates the HMAC. Preserve the raw bytes before any JSON middleware consumes them.

Acknowledge, then process

Verify, persist or enqueue, and return a 2xx response within five seconds. Move database fan-out, AI work, outbound replies, and third party calls into an asynchronous worker.

  1. 1

    Immediate

  2. 2

    10 seconds

  3. 3

    60 seconds

  4. 4

    5 minutes

Non-2xx responses and timeouts are retried on this schedule. After the final attempt, the delivery remains available in logs for inspection and manual redelivery.

Test and inspect deliveries

  • Use Send test event to check reachability, HTTP status, and signature handling.
  • Use the Simulator to send realistic signed payloads through the delivery pipeline.
  • Open Delivery Logs to inspect the full payload, response, attempts, and client.
  • Use Resend after fixing the receiver; expect your deduplication layer to see the same event.
Open simulatorOpen delivery logs
Building with a coding assistant?

Copy the integration contract into your assistant, then let it tailor the implementation to your stack.

You are helping me receive Intelli Partner API webhooks in my product — events from the WhatsApp and Instagram accounts connected through Intelli (my clients' accounts, or my own).

Before writing code, ask me:
1. My backend framework/language.
2. Whether I already have a public HTTPS endpoint for webhooks.

Setup:
- I configure the receiver URL and event selection in the Intelli portal (Webhooks page). INTELLI_WEBHOOK_SECRET (shown there) signs every delivery; keep it server-side.

A) Verify EVERY delivery before parsing:
- Read the X-Intelli-Signature header, compute HMAC-SHA256 over the RAW request body with INTELLI_WEBHOOK_SECRET, and constant-time compare against "sha256=<hex digest>". Reject mismatches with 401. Never parse the JSON before the signature checks out, and never verify against a re-serialized body — byte-for-byte raw only.

B) Respond fast, process async, stay idempotent:
- Return 2xx immediately and hand the payload to a queue/worker. Failed or slow deliveries are retried on my configured retry policy, so deliveries can arrive more than once and out of order — dedupe on the message id inside the payload.

C) Events — every payload carries event, channel and client_ref so I can route to the right account:
- message.received (WhatsApp, Instagram): an end user wrote in. WhatsApp media arrives as an id — fetch it with GET /media/{media_id}?client_ref=... while it's valid (30 days). Instagram media carries a short-lived URL at attachments[].payload.url — download on receipt.
- message.status (WhatsApp): a message I sent moved to sent, delivered, read or failed — this is where ASYNCHRONOUS delivery failures surface (e.g. a header media link Meta couldn't fetch). Store the status against my message_id and alert on failed.
- message.reaction (WhatsApp, Instagram): a reaction was added or removed.
- message.echo (Instagram): the connected professional account itself sent something.
- message.read (Instagram): the user read my message.
- message.postback (Instagram): the user activated a postback action.
- template.status (WhatsApp): Meta finished reviewing a template — carries template.status (APPROVED, REJECTED, PAUSED, …) and template.reason. Flip my UI to sendable on APPROVED; show the reason on REJECTED.
- user.preferences (WhatsApp): the user opted in or out of marketing — honor it before sending marketing templates.
- webhook.test: a signed diagnostic event I can fire from the dashboard.

D) Tooling while I build:
- The portal's webhook simulator fires real signed sample events at my endpoint, and Delivery Logs show every attempt (with a resend button) so I can debug without waiting for real traffic.

Write idiomatic, production-quality code for my stack: a verified receiver, an async processor with idempotency, and handlers for the events I use.
PreviousHosted onboarding
NextMessages