Skip to content

Webhooks Reference

ClinicFlow processes real-time external events using secure HTTP webhook endpoints. Incoming webhooks are verified via cryptographic signatures before payload execution.


  • Path: POST /webhooks/whatsapp
  • Verification Path: GET /webhooks/whatsapp
  • Public URL: https://api.clinicflow.lat/webhooks/whatsapp
  • Authentication: HMAC-SHA256 Payload Signature (X-Hub-Signature-256)

When configuring the webhook in the Meta Developer Console, Meta sends a GET request with challenge query parameters.

Query Parameters:

  • hub.mode: Set to subscribe.
  • hub.verify_token: Must match WHATSAPP_WEBHOOK_VERIFY_TOKEN.
  • hub.challenge: Random challenge string.

Success Response: Returns hub.challenge as plain text with HTTP status 200 OK.

Sent by Meta whenever a patient messages a clinic’s WhatsApp number.

Every POST request contains an X-Hub-Signature-256 header formatted as sha256=<signature>.

  1. The backend computes the HMAC-SHA256 hash of the raw HTTP request body using WHATSAPP_APP_SECRET.
  2. The signature is compared using crypto.timingSafeEqual to prevent timing attacks.
  3. Requests failing signature comparison are rejected with 401 Unauthorized.
  1. 24-Hour Event Deduplication: Inbound message IDs (wamid) are cached in Redis (dedup:wamid:<id>) for 24 hours. Duplicate deliveries are acknowledged (200 OK) and ignored.
  2. Per-Phone Concurrency Lock: Distributed Redis locks (lock:phone:<phone_number>) ensure sequential message handling when a patient sends multiple rapid messages.
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "100234567890123",
"changes": [
{
"value": "messages",
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "+50370001122",
"phone_number_id": "109876543210987"
},
"contacts": [
{
"profile": { "name": "Carlos Gomez" },
"wa_id": "50370001122"
}
],
"messages": [
{
"from": "50370001122",
"id": "wamid.HBgMNTAzNzAwMDExMjIVAgARGBI1RkUzNDVDN0U4OTAxM0EzAA==",
"timestamp": "1773800000",
"text": { "body": "Hola, quisiera agendar una cita de limpieza" },
"type": "text"
}
]
}
}
]
}
]
}

  • Path: POST /webhooks/lemonsqueezy
  • Public URL: https://api.clinicflow.lat/webhooks/lemonsqueezy
  • Authentication: HMAC-SHA256 Signature (X-Signature)

LemonSqueezy signs every webhook payload with the LEMONSQUEEZY_SIGNING_SECRET. The backend computes the HMAC-SHA256 signature over the raw request body and verifies it using crypto.timingSafeEqual.

Fired when a clinic completes a SaaS subscription purchase or upgrade.

{
"meta": {
"event_name": "order_created",
"custom_data": {
"clinic_id": "8f3b2c1a-4e5f-6a7b-8c9d-0e1f2a3b4c5d"
}
},
"data": {
"type": "orders",
"id": "123456",
"attributes": {
"order_number": 1001,
"user_email": "admin@clinicasanfrancisco.com",
"status": "paid",
"total": 4900,
"first_order_item": {
"variant_id": 98765,
"variant_name": "Pro Tier Monthly"
}
}
}
}

Actions Executed:

  1. Validates custom_data.clinic_id.
  2. Updates clinics.subscription_plan (basic, pro, clinica).
  3. Updates clinics.subscription_expires_at to the upcoming billing cycle end date.
  4. Logs subscription order details in ls_orders.

Fired on recurring subscription renewals or plan cancellations.

Actions Executed:

  • Active / Renewed: Extends subscription_expires_at.
  • Cancelled / Past Due: Sets subscription status to grace period or triggers feature lock (downgradeReconciliationJob.ts).

  1. Fast Response Acknowledgment: Webhook endpoints return 200 OK within 500ms before triggering long-running processing tasks. Heavy workloads are delegated to BullMQ workers.
  2. Idempotency Guarantee: All handlers check event uniqueness using database unique constraints (ls_orders.order_id) or Redis keys before mutating database state.