Webhooks
Table of contents¶
- Introduction
- How they are enabled
- How delivery works
- Available webhook types
- Payloads
- Contact created and contact updated
- Contact unsubscribed
- Loyalty level changed
- Loyalty data updated
- Invalid email
- Movement created and movement updated
Introduction¶
Webhooks let an external system (PMS, CRM, CDP, booking engine…) receive changes made in Fideltour in real time, without having to poll the API.
When an event happens in Fideltour (a contact is created or updated, their loyalty level changes, a booking is registered…), Hotel Data Hub (HDH) receives it and forwards it with a POST request to the URL provided by the external system.
Webhooks cover the direction from Fideltour to the external system. For the opposite direction (sending data to Fideltour), use the API endpoints: Contacts, Movements, Consents and Loyalty.
How they are enabled¶
For integrations with external systems, the HDH integrations team sets up the webhooks manually (the REST hooks subscriptions described in Zapier and n8n are exclusive to those apps). To enable them, the external system provides us with:
- The URL where it wants to receive each webhook type. It can be the same URL for all of them or a different one per type.
- The webhook types it needs (see the table).
- The hotel chain it wants to receive events from.
- Basic authentication credentials (username and password), only if needed for the types that support it (see How delivery works).
Once configured, events start arriving as soon as they happen; historical data is not sent.
How delivery works¶
- Method and format:
POSTwith a JSON body (Content-Type: application/json). - One event per request: each request contains a single JSON object (one contact or one movement), never a list.
- Expected response: the external system must respond with a
200. Any other status code, a network error or no response is logged in HDH as a failed delivery. - Timeout: 5 seconds to establish the connection and 30 seconds to receive the response. We recommend responding
200as soon as the event is received and processing it asynchronously afterwards. - No retries: a failed delivery is not retried. If the external system needs to recover missing data, it can query the Contacts or Movements API.
- Order: events are sent asynchronously and their arrival order is not guaranteed.
- Authentication: by default, requests carry no authentication. The Loyalty level changed and Loyalty data updated types can be sent with basic authentication (
Authorization: Basic …) if a username and password are provided.
Available webhook types¶
| Type | When it fires | Content |
|---|---|---|
| Contact created | A contact is created in Fideltour | Full contact record + hotel_chain |
| Contact updated | Fideltour notifies a contact update | Full contact record + hotel_chain |
| Contact unsubscribed | The contact unsubscribes from commercial communications | Reduced payload (event: "unsubscribe") |
| Loyalty level changed | The contact changes level in the loyalty program | Reduced payload (event: "level_updated") |
| Loyalty data updated | The contact's loyalty data changes (points, level, card…) | Full contact record + hotel_chain + event: "loyalty_updated" |
| Invalid email | The contact's email becomes invalid (bounce, wrong address…) | Full contact record + hotel_chain + event: "email_invalid" |
| Movement created | A movement (booking, stay…) is created in Fideltour | Full movement record (includes hotel_chain) |
| Movement updated | Fideltour notifies a movement update (status, dates, amount…) | Full movement record (includes hotel_chain) |
In every payload, hotel_chain is the hotel chain code in Fideltour.
Payloads¶
The examples use fictitious data. Fields that are numeric identifiers (province, town, level, custom_tags, hotels, room_type, regime, agency…) refer to Fideltour objects; they are described on the Contacts and Movements pages.
Fideltour may add new fields to contacts or movements over time. We recommend that the external system ignore unknown fields rather than reject the request.
Contact created and contact updated¶
Full contact record, as stored in Fideltour, plus hotel_chain. Some useful fields:
custom_tags/custom_tags_name: the contact's tags (identifiers and names).subscribed,gdpr_consent_personalized_commercial,whatsapp_subscribed_commercial,sms_subscribed_commercial…: consent status (see Consents).email_status: 0 = new, 1 = valid, 2 = verified, 3 = bounced, 4 = hard bounced.last_sent,last_opened,last_click: date of the last email sent, opened and clicked.external_object_id: the contact's identifier in the external system, if it was provided on creation.custom_fields: the chain's custom fields.
{
"id": 12345678,
"hotel_chain": "ABCD",
"external_object_id": "PN-000123",
"name": "Laura",
"surname": "García López",
"email": "laura.garcia@example.com",
"email_status": 1,
"phone1": "+34 600000000",
"phone1_e164": "+34600000000",
"gender": 2,
"birthday": "1985-03-12",
"language": "ES",
"country": "ES",
"province": 100001,
"town": 200001,
"zone": null,
"address": "Calle Mayor 1",
"post_code": "07001",
"identification_document_type": 1,
"identification_number": "00000000T",
"business_name": null,
"company_name": null,
"work_place": null,
"business_address": null,
"fiscal_address": null,
"source": 4,
"notes": null,
"photo": null,
"subscribed": true,
"date_subscribed": "2026-01-15T10:20:30.000000",
"modification_date_subscribed": "2026-01-15T10:20:30.000000",
"unsubscribe_reason": null,
"gdpr_consent_personalized_commercial": false,
"whatsapp_status": 0,
"whatsapp_enabled": true,
"whatsapp_subscribed_commercial": true,
"whatsapp_subscribed_commercial_personalized": false,
"sms_status": 0,
"sms_enabled": true,
"sms_subscribed_commercial": true,
"sms_subscribed_commercial_personalized": false,
"has_webpush_device": false,
"last_hlr_status": null,
"last_hlr_validated_at": null,
"custom_tags": [300001, 300002],
"custom_tags_name": ["Familia", "Repetidor"],
"topics": [],
"hotels": [1001, 1002],
"level": 400001,
"level_name": "Gold",
"loyalty_card_number": "0000000012345678",
"has_password": true,
"points": 120,
"level_points": 120,
"value": 1,
"loyalty_nights": 4,
"loyalty_bookings": 1,
"loyalty_last_booking_date": "2026-08-20",
"loyalty_custom_tag_timestamp": null,
"last_level_review": null,
"kicked_out_loyalty": null,
"is_profile_completed": false,
"total_trips": 3,
"total_bookings": 2,
"review": null,
"promo_code": null,
"app_user_id": null,
"last_sent": "2026-10-01T09:00:00.000000",
"last_opened": "2026-10-01T10:30:00.000000",
"last_click": null,
"last_conversion": null,
"company": null,
"is_active": true,
"created_at": "2026-01-15T10:20:30.000000",
"updated_at": "2026-10-01T10:30:00.000000",
"custom_fields": {}
}
Contact unsubscribed¶
{
"hotel_chain": "ABCD",
"contact": 12345678,
"email": "laura.garcia@example.com",
"event": "unsubscribe"
}
contact is the contact's identifier in Fideltour. If the rest of the record is needed, it can be retrieved from the Contacts API.
Loyalty level changed¶
{
"hotel_chain": "ABCD",
"contact": 12345678,
"email": "laura.garcia@example.com",
"level": 400001,
"level_name": "Gold",
"event": "level_updated"
}
Loyalty data updated¶
Full contact record (same as in Contact created and contact updated), with the updated loyalty data (level, level_name, points, level_points, loyalty_card_number…), plus:
{
"hotel_chain": "ABCD",
"event": "loyalty_updated"
}
Invalid email¶
Full contact record (same as in Contact created and contact updated), with the new email_status, plus:
{
"hotel_chain": "ABCD",
"event": "email_invalid"
}
Movement created and movement updated¶
Full movement record as stored in Fideltour. Some useful fields:
contact/contact_email: the contact the movement belongs to.hotel: the movement's hotel in Fideltour.booking_type,statusandsub_status: movement type and status (see Movements).localizerandtrip_localizer: booking and trip locators.external_object_id: the movement's identifier in the source system.
{
"id": 1000001,
"hotel_chain": "ABCD",
"hotel": 1001,
"contact": 12345678,
"contact_email": "laura.garcia@example.com",
"contact_is_active": true,
"booking_type": 0,
"status": 1,
"sub_status": 2,
"localizer": "RES-2026-000123",
"trip_localizer": "RES-2026-000123",
"external_object_id": "RES-2026-000123",
"trip": 500001,
"date": "2026-10-01T12:00:00",
"entrance": "2026-12-01",
"departure": "2026-12-05",
"is_checkin_realized": false,
"is_checkout_realized": false,
"adults": 2,
"juniors": 0,
"childs": 1,
"babies": 0,
"amount": "850.00",
"currency": "EUR",
"room_number": null,
"room_type": 600001,
"contracted_room_type": null,
"upgraded_room_type": null,
"regime": 700001,
"contracted_regime": null,
"upgraded_regime": null,
"fare_type": 800001,
"category": null,
"agency": null,
"trip_agency": null,
"event_type": null,
"package": null,
"offer": null,
"promo_code": null,
"input_channel": 4,
"original_input_channel": null,
"complex": null,
"stay_complex": null,
"trip_billing_complex": null,
"trip_billing_room_type": null,
"trip_stay_complex": null,
"trip_stay_room_type": null,
"total_rooms": null,
"level": null,
"notes": null,
"url": null,
"mac_addr": null,
"link_click": null,
"is_from_quote": null,
"is_processed_loyalty": false,
"trustyou_sent": false,
"updated_at": "2026-10-01T12:00:00.000000",
"custom_fields": {}
}