Skip to content

Webhook entry points

Four webhook endpoints. Three inbound from Beds24 / Mailgun / Twilio; one self-loopback guard.

/webhook/beds24 (SYNC_ROOM)

Beds24’s per-room sync notification. Fires on new / modified / cancelled bookings + inventory / price changes.

Payload is a “poke” — {roomId, propId, ownerId, action: "SYNC_ROOM"}. No actionable data; we fetch via the v2 API.

Currently logging only (webhook.beds24.received in migration_log). The booking-event push below carries the actionable signal.

Auth: bearer BEDS24_WEBHOOK_SECRET via Authorization header.

/webhook/beds24/booking (booking-event push)

Beds24’s per-booking event push. The active spawn signal.

Fires on:

  • status=new — new booking
  • status=modify — booking modified (including our own writes — see debounce below)
  • status=cancel — booking cancelled
  • status=message — new OTA message

Method: GET (not POST — Beds24 sends Booking Notifier user-agent GETs with ?bookid=X&status=Y). Our handler accepts both methods.

  • For new / cancel: always calls ensureBookingFlagWorkflow
  • For modify: checks the self-write debounce (60s KV TTL after our own updateBooking) — if present, skip the respawn
  • For message: logged only

Auth: same bearer token as SYNC_ROOM.

/webhook/mailgun

Mailgun events webhook V2. Receives accepted / delivered / opened / clicked / failed / rejected / complained / unsubscribed.

JSON body with signature block (timestamp + token + HMAC-SHA256) + event-data block. We verify the signature, dedup on event-data.id, insert into mailgun_events.

Side effects:

  • Terminal events (delivered, failed, rejected) sync into outbound_log for the watchdog
  • Permanent bounces + spam complaints SMS Bill

Auth: HMAC-SHA256 of timestamp + token keyed by MAILGUN_WEBHOOK_SIGNING_KEY. 15-min skew window blocks replay.

/webhook/twilio/status

Twilio Message status callback. Receives queued → sending → sent → delivered (success) or failed / undelivered (failure).

Form-urlencoded body with MessageSid, MessageStatus, To, ErrorCode, ErrorMessage. We verify signature, dedup on (MessageSid, MessageStatus), insert into twilio_events.

Terminal states sync delivered_at / failed_at to outbound_log. Permanent failures SMS Bill.

Auth: HMAC-SHA1 (Twilio’s scheme) of URL + sorted form params, keyed by TWILIO_AUTH_TOKEN. X-Twilio-Signature header.

Self-write debounce

When our worker calls updateBooking, Beds24 fires a modify push within seconds. Without protection this respawns the workflow that just made the write — infinite cascade.

Fix (in src/beds24/client.ts):

  • After every successful updateBooking, set BEDS24_KV.put("self-write:{bookingId}", "1", { expirationTtl: 60 })
  • /webhook/beds24/booking checks the KV key before respawning on modify — if present, log webhook.booking.modify.skipped_self_write and return

new and cancel always respawn — they’re not echoes.

Notes:

  • 60s TTL covers Beds24’s typical callback latency + buffer
  • KV failures are best-effort; one unnecessary respawn ≠ correctness problem (phases are idempotent)
  • SYNC_ROOM (/webhook/beds24) does NOT loop back from our writes — only the booking-event push does. See memory file beds24-api-no-webhook-loopback for the empirical verification.