Set up auto-purchase
Collect consent and arm a hotel watch to purchase within the user's price cap.
In short
2 calls, in the order this page makes themFor people and agents- 1POST
/v1/watcher/travel/hotel/watchesCreate a hotel watch - 2POST
/v1/watcher/travel/hotel/watches/{watch_id}/confirm-purchaseConfirm auto-purchase consent
The same list heads this page's Markdown:/watcher/auto-purchase.md
Auto-purchase lets an armed hotel watch book and charge the user when the price reaches their limit. Use it only after the user has chosen a specific hotel and explicitly agreed to the stay, travelers, payment method, and maximum total.
Trip watches cannot auto-purchase. They always notify.
Before you begin
You need:
- A working hotel-watch integration. If you have not created one yet, follow Create your first price watch.
- An active OEX payment instrument that belongs to the acting user.
- The travelers' names and a booking contact.
- A clear record of the user's instruction to purchase within a price cap.
- A webhook receiver subscribed to purchase outcome events.
Auto-purchase can move real money without the user being online. Never confirm a purchase mandate until you have shown the user exactly what will be booked and received their explicit approval.
How consent protects the user
Arming auto-purchase takes two API calls:
- Create the watch. Watcher validates the payment instrument and drafts a mandate capped at
trigger.amount. The watch entersauto_book_pending. - Confirm the purchase. After your application shows the user the final consent details, confirm the draft within 15 minutes. The watch then enters
auto_book.
While confirmation is pending, the watch behaves like a notification watch. A qualifying price sends travel.hotel.watch.triggered; Watcher never books or charges.
1. Collect purchase details and consent
Collect these details together:
- The hotel, stay dates, occupancy, currency, and guest nationality.
- The maximum total for the stay.
- One active
payment_instrument_id. - Between 1 and 8 travelers.
- A booking contact with an email address and phone number.
- The user's own words authorizing the purchase, when available.
The consent evidence can contain:
human_messages— the user's relevant messages.conversation_context— a short description that makes those messages understandable later.
For this example, Maya has agreed to book the Tokyo stay automatically at 450 USD or less.
2. Create a pending auto-purchase watch
Add a complete purchase object to the hotel-watch request:
curl -X POST "https://api.oex.sh/v1/watcher/travel/hotel/watches" \
-H "content-type: application/json" \
-H "x-api-key: $OEX_API_KEY" \
-d '{
"hotel_id": "hb_123456",
"check_in": "2026-09-10",
"check_out": "2026-09-13",
"occupancy": [
{"adults": 2}
],
"currency": "USD",
"guest_nationality": "US",
"trigger": {
"type": "below_total",
"amount": 450
},
"purchase": {
"payment_instrument_id": "pi_abc123",
"travelers": [
{
"first_name": "Maya",
"last_name": "Chen",
"email": "maya@example.com",
"is_lead": true
},
{
"first_name": "Jordan",
"last_name": "Lee",
"email": "jordan@example.com"
}
],
"contact": {
"first_name": "Maya",
"last_name": "Chen",
"email": "maya@example.com",
"phone": "+14155550123"
},
"evidence": {
"human_messages": [
"Book it automatically if the total drops to 450 USD or less."
],
"conversation_context": "User selected the Tokyo stay for 10–13 September 2026."
}
}
}'The response includes the normal watch fields plus the pending mandate. Relevant fields are shown here:
{
"data": {
"id": "wtc_01hq3example",
"status": "active",
"mode": "auto_book_pending",
"hotel_id": "hb_123456",
"check_in": "2026-09-10",
"check_out": "2026-09-13",
"currency": "USD",
"trigger": {
"type": "below_total",
"amount": 450
},
"mandate_id": "mdt_0123456789abcdef0123456789abcdef",
"payment_instrument_id": "pi_abc123",
"mandate_status": "pending",
"confirm_expires_at": "2026-08-25T09:30:00+00:00",
"current_total": 512.4
}
}Save confirm_expires_at and complete the user confirmation before that time.
An unknown or inactive payment instrument returns 422. If the mandate service is unavailable, the request returns 502 and no consent shortcut is taken.
3. Show the user the confirmation
Before confirming, show the user:
- The hotel and stay dates.
- Every traveler.
- The booking contact.
- The selected payment method.
- The currency and maximum total of 450 USD.
- That the purchase will happen automatically when a qualifying price is available.
Do not treat the user's earlier interest in a price alert as purchase consent. Ask for a separate, explicit confirmation.
4. Confirm within 15 minutes
After the user approves, confirm the pending mandate:
curl -X POST "https://api.oex.sh/v1/watcher/travel/hotel/watches/wtc_01hq3example/confirm-purchase" \
-H "content-type: application/json" \
-H "x-api-key: $OEX_API_KEY" \
-d '{
"confirmation": {
"channel": "sms",
"message_id": "msg_42",
"confirmed_by": "usr_maya"
}
}'Watcher records confirmation with the mandate and returns the armed watch:
{
"data": {
"id": "wtc_01hq3example",
"status": "active",
"mode": "auto_book",
"mandate_id": "mdt_0123456789abcdef0123456789abcdef",
"mandate_status": "active"
}
}The request body is optional, but include your confirmation record when you have one.
If the confirmation window expired or the draft was revoked, Watcher returns 422 and leaves the watch pending. Delete it and create a new watch to collect fresh consent. Confirming a watch with no pending draft returns 409.
5. Handle the outcome
When an armed watch first sees a qualifying price, its status changes to purchasing. Watcher then:
- Fetches a fresh offer.
- Verifies the mandate.
- Holds the booking and obtains the binding total.
- Checks the binding total against the user's cap.
- Exercises the mandate and completes the booking.
If the fresh offer is unavailable or has moved above 450 USD, Watcher returns the watch to active and keeps checking. This is normal price movement, so no purchase outcome event is sent.
If the booking completes, Watcher sends travel.hotel.watch.purchased. This example abbreviates data.watch to the fields most relevant to the outcome:
{
"id": "evt_01hq5purchased",
"type": "travel.hotel.watch.purchased",
"created_at": "2026-08-28T10:20:00+00:00",
"data": {
"watch": {
"id": "wtc_01hq3example",
"status": "purchased",
"mode": "auto_book",
"booking_id": "bkg_01hq3example"
},
"user_id": "usr_maya",
"booking": {
"id": "bkg_01hq3example",
"status": "CONFIRMED",
"total_amount": {
"amount": 438.7,
"currency": "USD"
}
},
"mandate_id": "mdt_0123456789abcdef0123456789abcdef"
}
}data.booking.total_amount is the binding amount charged. data.booking.due_at_property is also present when the hotel collects an additional amount at the property.
If Watcher cannot safely complete the purchase, it sends travel.hotel.watch.purchase_failed with data.reason and data.current when a fresh price was available. A binding hold price above the cap is a failure, and the mandate is not exercised. Tell the user what happened and offer the normal manual booking flow.
An armed watch sends a purchase outcome instead of travel.hotel.watch.triggered. Process both purchase events through the same verified, idempotent receiver described in Handle webhooks reliably.
Rearm or delete the watch
Rearming an auto-purchase watch creates a replacement draft mandate and returns the watch to auto_book_pending. Show the consent details again and call confirm-purchase before expecting another automatic booking.
You cannot rearm a watch while it is purchasing or after it is purchased; Watcher returns 409.
Deleting an auto-purchase watch revokes its mandate on a best-effort basis, including an unconfirmed draft. A watch currently purchasing cannot be deleted.
Launch checklist
Before enabling auto-purchase for users, verify that your application:
- Confirms the acting user with
whoami. - Accepts only an active payment instrument owned by that user.
- Collects complete traveler and contact details.
- Records the user's explicit words and relevant context.
- Shows the hotel, dates, travelers, payment method, currency, and cap before confirmation.
- Confirms before
confirm_expires_at. - Verifies and deduplicates every purchase event.
- Handles both
purchasedandpurchase_failed. - Offers a manual booking path after failure.