Inbound events & integrations

Cannahub does not expose a general-purpose, self-service outbound webhook system — there is no dashboard where you register a URL and subscribe to resource.created style events. Instead, the platform receives inbound events from the external providers it integrates with. This page documents what actually exists today.

The only inbound-event integration wired up today is WhatsApp Business, delivered through the KAPSO provider. Its endpoints live under /api/whatsapp/* and are documented in full on the WhatsApp API page.


WhatsApp Business (KAPSO)

Cannahub connects members to their club over WhatsApp. Messages and Flow interactions arrive from KAPSO (which sits in front of the Meta WhatsApp Cloud API and handles encryption, delivery and signing), and Cannahub responds through the same channel.

Loading diagram...

Message webhook

Receives inbound message events from KAPSO. The request carries KAPSO-specific headers, not Meta's raw webhook format:

  • Name
    X-Webhook-Event
    Type
    string
    Description

    The event name. Only whatsapp.message.received is processed; any other event is acknowledged and ignored.

  • Name
    X-Webhook-Signature
    Type
    string
    Description

    Plain-hex HMAC-SHA256 of the raw request body, keyed with the configured webhook secret. Verified before the payload is parsed.

  • Name
    X-Idempotency-Key
    Type
    string
    Description

    Provider-supplied key to help de-duplicate redelivered events.

The handler parses text, interactive (button/list) replies and template-button messages, dispatches them to the conversation handler, and always returns 200 so KAPSO does not retry on transient processing errors. An invalid signature returns 401.

Inbound KAPSO payload

{
  "message": {
    "id": "wamid.HBgLNTQ...",
    "from": "5491155551234",
    "type": "text",
    "text": { "body": "hola" }
  },
  "conversation": {
    "id": "conv_123",
    "phone_number": "5491155551234"
  },
  "phone_number_id": "1088..."
}

Response

{ "success": true }

Flows data endpoint

Backs a WhatsApp Flow — the in-chat form members use to link their club account. KAPSO handles encryption/decryption and calls this endpoint with an already-decrypted data_exchange payload; Cannahub responds with the standard Flow navigation format { version, screen, data }.

The endpoint drives a small state machine:

  • CLUB_SELECT → return the LOGIN screen for the chosen club.
  • LOGIN → authenticate the member against Medusa (/auth/customer/emailpass), fetch their profile, store the linked account, and return SUCCESS (or re-render LOGIN with an error).
  • COMPLETE action → return the SUCCESS screen.

Decrypted data_exchange

{
  "data_exchange": {
    "version": "3.0",
    "screen": "LOGIN",
    "action": "data_exchange",
    "flow_token": "5491155551234",
    "data": {
      "email": "member@club.com",
      "password": "•••••••",
      "club_name": "High Up"
    }
  }
}

Flow response

{
  "version": "3.0",
  "screen": "SUCCESS",
  "data": {
    "member_name": "Juan Pérez",
    "club_name": "High Up"
  }
}

Account linking

A short-lived code flow that links a logged-in web session to a WhatsApp number:

  • Name
    POST /api/whatsapp/link
    Description

    Called by the linking page after a successful login. Validates the body with Zod (customerId, tenantId, email, name, token) and returns { code } (201). The code expires after 10 minutes.

  • Name
    GET /api/whatsapp/link/{code}
    Description

    Called by the bot when the member sends LINK-{code}. Validates and consumes the code, returning the linked-account payload, or 404 if the code is invalid/expired/already used.

See the WhatsApp API page for the full request/response reference.


Backend (Medusa / Strapi) events

Cannahub's backend runs on Medusa, whose module system emits internal events (e.g. order.placed, customer.created) consumed by in-process subscribers inside the Medusa service. These are an internal implementation detail of the backend — they are not relayed to the BFF as HTTP webhooks and are not exposed to API consumers. If a use case requires reacting to backend events from an external system, it has to be built explicitly; there is no generic subscription surface today.


Verifying inbound requests

The WhatsApp webhook is authenticated with an HMAC-SHA256 signature over the raw request body. Cannahub verifies the X-Webhook-Signature header against the configured webhook secret before processing anything; a mismatch is rejected with 401.

Signature verification (concept)

import crypto from 'crypto'

const signature = req.headers['x-webhook-signature']
const expected = crypto
  .createHmac('sha256', WEBHOOK_SECRET)
  .update(rawBody) // the raw, unparsed request body
  .digest('hex')

if (crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
  // Request is verified
} else {
  // Reject with 401
}

Was this page helpful?