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.
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
- Your PMS sends the reservation as JSON to the host's webhook URL, with the host's token.
- StreamHost replies straight away to confirm it was received.
- StreamHost finds the room from your
unit_id, creates or updates the reservation, and schedules the guest's messages.
Quick start
- 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.
- 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. - 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.
- Go live. Send
reservation.created,reservation.updatedandreservation.deletedevents to the webhook URL. - Check deliveries. The same dashboard window lists every request StreamHost received and whether it was accepted.
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
Authorizationheader as-is, with noBearerorTokenin front.
Authorization: 3f9c1e...a07bOne 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
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
eventstringRequiredWhat happened:
reservation.created,reservation.updatedorreservation.deleted.e.g.
"reservation.created"dataobjectRequiredThe reservation.
e.g.
{ … }
data
reservation_idstringRequiredYour 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_idstringRequiredYour 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_idstringRequiredYour PMS's id for the property. Kept for reference and troubleshooting.
e.g.
"prop_tower_a"check_instring (YYYY-MM-DD)RequiredArrival date. Pre-arrival and check-in messages are timed from it.
e.g.
"2026-11-01"check_outstring (YYYY-MM-DD)RequiredDeparture date, after
check_in. Check-out messages and housekeeping are timed from it.e.g.
"2026-11-04"guestobjectRequiredWho is staying, so StreamHost can message them. See
data.guestbelow.e.g.
{ name, phone, email }sourcestringOptionalWhere 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"statusstringOptionalThe 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_idstringOptionalThe OTA's own confirmation code (Airbnb, Booking.com…), shown to the host's staff.
e.g.
"HMABCD1234"unit_namestringOptionalThe room's name, shown to staff if
unit_idhasn't been mapped yet.e.g.
"A-12-03"property_namestringOptionalThe property's name, for reference.
e.g.
"Tower A"no_of_paxintegerOptionalNumber of guests, shown to staff and housekeeping. Defaults to 1.
e.g.
2
data.guest
namestringRequiredShown in the host's inbox and used to greet the guest in messages.
e.g.
"Aisha Rahman"phonestringRequiredThe guest's WhatsApp number, with country code. If the guest has no phone, send
emailinstead.e.g.
"+60123456789"emailstringOptionalUsed for email messages, and to reach guests who have no phone.
e.g.
"aisha@example.com"idstringOptionalThe 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 send | Result |
|---|---|
anything containing cancel, no_show, no show, noshow | Booking cancelled |
Arrived, Arrival, Check-In, Checked-In, Checked In, In House, InHouse | Guest marked checked in |
Departed, Departure, Check-Out, Checked-Out, Checked Out | Guest marked checked out; housekeeping is notified |
Confirmed, Reserved, Pending, or anything else | No change |
Source (OTA) slugs
Send one of these exact values in source (not case-sensitive). bookingcom works; Booking.com doesn't.
| Slug | Channel |
|---|---|
airbnb | Airbnb |
bookingcom | Booking.com |
agoda | Agoda |
agodahomes | Agoda Homes |
expedia | Expedia |
vrbo | VRBO |
traveloka | Traveloka |
tiketcom | Tiket.com |
ctripcm | Ctrip / Trip.com |
streamhost | Direct / walk-in booking |
hostplatform | HostPlatform |
facebook | |
instagram | |
tiktok | TikTok |
xhs | Xiaohongshu (RED) |
youtube | YouTube |
lemon8 | Lemon8 |
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
| Status | Body | Meaning |
|---|---|---|
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
400or401. 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 unmappedunit_idor an unknownsource.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).
{
"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 noBearer. - Every room's External ID in StreamHost equals the
unit_idyou send. reservation_idstays the same for the life of the booking.- Dates are
YYYY-MM-DD, andcheck_outis aftercheck_in. - Guest phone includes the country code.
sourceuses the slugs listed above.- Cancellations are sent as
reservation.deleted. - Network errors and
5xxare retried;400and401are 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.