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 bookingstatus=modify— booking modified (including our own writes — see debounce below)status=cancel— booking cancelledstatus=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 callsensureBookingFlagWorkflow - For
modify: checks the self-write debounce (60s KV TTL after our ownupdateBooking) — 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 intooutbound_logfor 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, setBEDS24_KV.put("self-write:{bookingId}", "1", { expirationTtl: 60 }) /webhook/beds24/bookingchecks the KV key before respawning onmodify— if present, logwebhook.booking.modify.skipped_self_writeand 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 filebeds24-api-no-webhook-loopbackfor the empirical verification.