Developers · Last verified 2026-10-01

PMS Reservation Webhook

Push reservations from any property management system into StreamHost: endpoint, authentication, every field and why it matters, and a live dry run.

Open .md
Required always sendOptional send if you have it

Overview

Send StreamHost one HTTP request every time a reservation is created, changed or cancelled in your PMS. StreamHost keeps the host's reservations in sync and takes care of the rest: WhatsApp messages to the guest, deposit collection, the pre-arrival guest-information request and housekeeping.

The integration is push-only. StreamHost never calls your PMS, and you never need to poll.

How a booking flows

  1. Your PMS sends the reservation as JSON to the host's webhook URL, with the host's token.
  2. StreamHost replies straight away to confirm it was received.
  3. StreamHost finds the room from your unit_id, creates or updates the reservation, and schedules the guest's messages.

Quick start

  1. Get the URL and token. The host opens the StreamHost dashboard → Connect your own PMS → Generate token, and sends you the Webhook URL and the token shown there. The token is shown only once.
  2. Map rooms. In Buildings, the host sets each unit's External ID to the room id your PMS uses. That's what you send as unit_id.
  3. Test it. Paste the token into the Try it panel on this page and run an example. Nothing is saved and no guest is messaged.
  4. Go live. Send reservation.created, reservation.updated and reservation.deleted events to the webhook URL.
  5. Check deliveries. The same dashboard window lists every request StreamHost received and whether it was accepted.
Example request
curl -X POST '{{WEBHOOK_URL}}' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: <TOKEN>' \
  -d '{
    "event": "reservation.created",
    "data": {
      "reservation_id": "res_10293",
      "property_id": "prop_tower_a",
      "unit_id": "unit_a_12_03",
      "guest": { "name": "Aisha Rahman", "phone": "+60123456789" },
      "check_in": "2026-11-01",
      "check_out": "2026-11-04",
      "source": "airbnb"
    }
  }'

URL & authentication

Both values come from the host's StreamHost dashboard (Connect your own PMS):

  • Webhook URL: where you send requests. Shown as {{WEBHOOK_URL}} throughout these docs.
  • Token: identifies the host. Send it in the Authorization header as-is, with no Bearer or Token in front.
Header
Authorization: 3f9c1e...a07b

One token per host. The token is how StreamHost knows which host a booking belongs to. If you serve several hosts, store each host's token and send each host's bookings with their own.

Keep it secret. Treat the token like a password: keep it on your server, send it only over HTTPS, and ask the host to rotate it if it leaks. After a rotation the old token stops working immediately, so switch to the new one first.

Endpoint

Request
POST {{WEBHOOK_URL}}
Content-Type: application/json
Authorization: <TOKEN>

One URL for every event. The event field says what happened.

Payload fields

The body has two keys, event and data. Always send the full current reservation, not just the fields that changed.

Envelope

  • eventstringRequired

    What happened: reservation.created, reservation.updated or reservation.deleted.

    e.g. "reservation.created"

  • dataobjectRequired

    The reservation.

    e.g. { … }

data

  • reservation_idstringRequired

    Your PMS's id for the booking. Keep it the same for the life of the booking; StreamHost uses it to recognise updates and cancellations, so sending the same booking twice never creates a duplicate.

    e.g. "res_10293"

  • unit_idstringRequired

    Your PMS's id for the room. Must match the External ID the host set on the unit in StreamHost. It tells StreamHost which room and building the guest is in, which decides the door code, WiFi, check-in instructions and the WhatsApp number used.

    e.g. "unit_a_12_03"

  • property_idstringRequired

    Your PMS's id for the property. Kept for reference and troubleshooting.

    e.g. "prop_tower_a"

  • check_instring (YYYY-MM-DD)Required

    Arrival date. Pre-arrival and check-in messages are timed from it.

    e.g. "2026-11-01"

  • check_outstring (YYYY-MM-DD)Required

    Departure date, after check_in. Check-out messages and housekeeping are timed from it.

    e.g. "2026-11-04"

  • guestobjectRequired

    Who is staying, so StreamHost can message them. See data.guest below.

    e.g. { name, phone, email }

  • sourcestringOptional

    Where the booking came from, as one of the slugs in Source (OTA) slugs. Hosts turn deposits and the guest-information request on or off per channel, so send it whenever you know it.

    e.g. "airbnb"

  • statusstringOptional

    The booking's current state, used to mark the guest checked in or out. A status containing "cancel" or "no show" cancels the booking. See Status values.

    e.g. "Checked-In"

  • ota_idstringOptional

    The OTA's own confirmation code (Airbnb, Booking.com…), shown to the host's staff.

    e.g. "HMABCD1234"

  • unit_namestringOptional

    The room's name, shown to staff if unit_id hasn't been mapped yet.

    e.g. "A-12-03"

  • property_namestringOptional

    The property's name, for reference.

    e.g. "Tower A"

  • no_of_paxintegerOptional

    Number of guests, shown to staff and housekeeping. Defaults to 1.

    e.g. 2

data.guest

  • namestringRequired

    Shown in the host's inbox and used to greet the guest in messages.

    e.g. "Aisha Rahman"

  • phonestringRequired

    The guest's WhatsApp number, with country code. If the guest has no phone, send email instead.

    e.g. "+60123456789"

  • emailstringOptional

    Used for email messages, and to reach guests who have no phone.

    e.g. "aisha@example.com"

  • idstringOptional

    The guest's WhatsApp or WeChat user id, if your PMS has it. Helps StreamHost recognise a returning guest who messages from a different number. Not your internal guest id.

    e.g. "US.13491208655302741918"

Events

reservation.created

A new booking. StreamHost adds it and schedules the guest's messages. Sending it again for the same reservation_id is safe.

reservation.updated

Any change: dates, room, guest details, status. Send the full reservation; StreamHost works out what changed and only re-sends messages the change affects. Sending an update with no changes is safe.

reservation.deleted

The booking was cancelled. StreamHost permanently removes the reservation and its upcoming messages. The guest's chat history stays. If the same reservation_id is sent again later, it's treated as a new booking.

Status values

status is optional and not case-sensitive. If your PMS doesn't track these states, leave it out.

You sendResult
anything containing cancel, no_show, no show, noshowBooking cancelled
Arrived, Arrival, Check-In, Checked-In, Checked In, In House, InHouseGuest marked checked in
Departed, Departure, Check-Out, Checked-Out, Checked OutGuest marked checked out; housekeeping is notified
Confirmed, Reserved, Pending, or anything elseNo change

Source (OTA) slugs

Send one of these exact values in source (not case-sensitive). bookingcom works; Booking.com doesn't.

SlugChannel
airbnbAirbnb
bookingcomBooking.com
agodaAgoda
agodahomesAgoda Homes
expediaExpedia
vrboVRBO
travelokaTraveloka
tiketcomTiket.com
ctripcmCtrip / Trip.com
streamhostDirect / walk-in booking
hostplatformHostPlatform
facebookFacebook
instagramInstagram
tiktokTikTok
xhsXiaohongshu (RED)
youtubeYouTube
lemon8Lemon8

Any other value is grouped under "Other OTA". Send direct bookings as streamhost.

Matching rooms

unit_id must equal the External ID of a unit in the host's StreamHost account. The building comes from that unit.

If unit_id doesn't match any unit, the booking is still saved, in a holding area called Unsynced Units, so the host can see it and move the room to the right building. Later bookings for that room then go straight there.

WhatsApp number per building

Hosts with several WhatsApp numbers can give each building its own number in StreamHost. Guests are messaged from the number of the building their room is in, or from the host's default number if none is set.

Nothing to do on your side. There's no field for this. Send the correct unit_id and StreamHost picks the number. The Try it panel shows which number a booking will use.

Responses & errors

StatusBodyMeaning
200{"status": "ok"}Received.
400{"error": "invalid payload"}The body is missing a required field or isn't in the expected shape. Fix the payload; retrying the same body won't help.
401{"error": "unauthorized"}The token is missing or wrong. Check it isn't prefixed with Bearer .

Use the Try it panel or the dry run to see exactly what's wrong with a payload. The host can also see every request StreamHost received in their dashboard.

Retries & ordering

  • Retry on network errors, timeouts and 5xx. Sending the same event twice is always safe.
  • Don't retry 400 or 401. The same request will fail the same way.
  • Send events in order and always include the full current reservation. If two updates arrive out of order, the last one received wins.
  • After an outage, resend every active reservation as reservation.created. Nothing is duplicated.

Testing with dry run

POST {{DRY_RUN_URL}} (your webhook URL with validate/ added) takes the same token and body as the webhook, but saves nothing and messages no one. It returns:

  • would_respond: the response the webhook would give.
  • errors: everything that must be fixed, with the field it applies to.
  • warnings: things that will be accepted but probably aren't what you meant, such as an unmapped unit_id or an unknown source.
  • outcome: what would happen: created, updated or cancelled, which room and building, and which WhatsApp number messages the guest.

The Try it panel on this page uses it. You can call it from your own tests too (up to 300 requests an hour).

Dry-run response
{
  "would_respond": { "status": 200, "body": { "status": "ok" } },
  "errors": [],
  "warnings": [
    { "field": "data.source", "message": "'Booking.com' is not a recognised slug. …" }
  ],
  "outcome": {
    "action": "create",
    "reservation_exists": false,
    "unit": { "matched": true, "unit_name": "A-12-03", "building_name": "Tower A" },
    "whatsapp_number": { "source": "building", "display_phone_number": "+60 11-1111 2222", "verified_name": "Tower A Concierge" },
    "channel": { "value": "booking.com", "recognised": false },
    "lifecycle": null,
    "messages_scheduled": true
  }
}

Go-live checklist

  • Webhook URL and token taken from the host's dashboard, stored per host, never logged.
  • Token sent as-is in Authorization, with no Bearer.
  • Every room's External ID in StreamHost equals the unit_id you send.
  • reservation_id stays the same for the life of the booking.
  • Dates are YYYY-MM-DD, and check_out is after check_in.
  • Guest phone includes the country code.
  • source uses the slugs listed above.
  • Cancellations are sent as reservation.deleted.
  • Network errors and 5xx are retried; 400 and 401 are not.
  • The dry run shows no errors or warnings for a new, a changed and a cancelled booking.

Building this with an AI assistant? Copy the whole spec, or a ready-made prompt.

Open .md