Introduction
Welcome to the Hornblower API!! You can use our API to access Hornblower API endpoints.
We have language bindings in JavaScript! You can view code examples in the dark area to the right.
This document describes the standards used to establish connectivity between a Hornblower API and your system.
Authentication
Access control is achieved using HTTP’s basic access authentication. Hornblower expects for the Token to be included in API requests to the Hornblower API in a header that looks like the following:
Authorization: Basic MTM3Nzc1OmpmZGQzamdyc3NmZXdmcmZycmU4Zg==
Format
Request Format
Example
const url = "https://devshop.hornblower.com/api";
const res = await fetch(url, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: "Basic yourToken",
},
body: payload,
method: "POST",
});
The hornblower API expects regular HTTPS requests that contain data as JSON formatted payloads as per specifications. It is important that the HTTP Content-type header is set to all requests.
Please note as well that we always return a 200 HTTP status code, as long as the response contains an expected payload (this includes error codes).
Datetime Format
Points in time are expressed as strings using the ISO 8601 datetime format.
Example: 2019-10-30T18:30:00-07:00
We always expect the local time with its timezone as UTC offset.
Example:
hbsdogco_53
Ticket in San Francisco for the 30th of October 2019 at 18:30 (local time):
Expected:
2019-10-30T18:30:00-07:00
Not expected:
2019-10-30T21:30:00-04:00 / 2019-10-31T01:30:00-00:00
Endpoint
Tours
Get Tours Example
const apiUrl = "https://devshop.hornblower.com/api/gyg/1/get-tours";
const res = await fetch(apiUrl, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
method: "GET",
});
The above command returns JSON structured like this:
{
"data": {
"tours": [
{
"name": "Alcatraz Day Tour",
"productId": "hacco_1000016"
},
{
"name": "Chicago Seadog Lakefront Speedboat Tour",
"productId": "hbsdogco_53"
},
{
"name": "San Francisco Dinner Cruise",
"productId": "hbsdogco_53"
}
]
}
}
You can call this endpoint in order to get all available tours. Please contact us to set up tours for you if you don't see any tours returned.
Request sample
GET https://devshop.hornblower.com/api/gyg/1/get-tours
Pick Up Location List
Get Pick Up Location List Example
const apiUrl = `https://my.hornblower.com/api/gyg/1/get-pickupLocationList/?${productId}`;
const res = await fetch(apiUrl, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
method: "GET",
});
The above command returns JSON structured like this:
{
"data": {
"pickupLocationList": [
{
"address": "3475 S Las Vegas Blvd, Las Vegas, NV 89109, USA",
"latitude": "36.1194161",
"longitude": "-115.1707592",
"locationName": "Casino Royale (Best Western Plus)",
"hotelLocationId": "5893b243-87af-4d07-9acd-35620cf939f8",
"minutesPrior": "70",
"pickupInstructions": "Next door at Harrah's - outside of the Bus Depot"
},
{
"address": "3535 S Las Vegas Blvd, Las Vegas, NV 89109, USA",
"latitude": "36.1181779",
"longitude": "-115.1713151",
"locationName": "The LINQ",
"hotelLocationId": "5b3aa17b-85d2-442c-853d-b1b4e5da4a9e",
"minutesPrior": "70",
"pickupInstructions": "Next door at Harrah's - outside the Bus Depot"
}
]
}
}
You can call this endpoint in order to get all available pick up locations for each product.
Request sample
GET https://devshop.hornblower.com/api/gyg/1/get-pickupLocationList/?productId=mavlv_1108456
Parameter | Type | Description |
---|---|---|
productId | String | The ID of the requested product in the hornblower’s system. |
Response sample
Fields | Type | Description |
---|---|---|
address | String | The address of the pick up location |
latitude | String | The latitude of the pick up location |
longitude | String | The longitude of the pick up location |
locationName | String | The name of the pick up location |
hotelLocationId | String | The id of the pick up location |
minutesPrior | String | The minutes prior departure time of the pick up location |
pickupInstructions | String | The instruction of the pick up location |
Availability
Get Availabilities Example
const productId = "hbsdogco_53";
const fromDateTime = "2019-11-01T00:00:00-07:00";
const toDateTime = "2019-11-06T23:59:59-07:00";
const withProduct = true;
const priceBand = 'HB7'
const apiUrl = "https://devshop.hornblower.com/api/gyg/1/get-availabilities";
const url = `${apiUrl}/?productId=${productId}&fromDateTime=${fromDateTime}&toDateTime=${toDateTime}&withProduct=${withProduct}&weight=${weight}&COUNT_ADULT=${adultCount}&COUNT_CHILD=${childCount}&priceBand=${priceBand}`;
const res = await fetch(url, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
method: "GET",
});
The above command returns JSON structured like this:
{
"data": {
"availabilities": [
{
"dateTime": "2020-08-05T12:00:00-05:00",
"productId": "hbsdogco_53",
"vacancies": 30,
"cutoffSeconds": 18000,
"ticketsData": [
{
"taxes": [
{
"TaxIncluded": false,
"id": "24024",
"FixedTax": 0,
"TaxPercentage": 0,
"TaxName": "City Amusement Tax"
},
{
"TaxIncluded": false,
"id": "24025",
"FixedTax": 0,
"TaxPercentage": 0,
"TaxName": "Sales Tax"
}
],
"ticketPrice": 20.27,
"ticketDescription": "Adult",
"isTaxInclusive": false,
"category": "ADULT",
"ageRange": {
"min": 13,
"max": 54
}
}
],
"options": [
{
"type": "fromStopId",
"id": 682,
"name": "Swanage Pier"
},
{
"type": "toStopId",
"id": 683,
"name": "Poole Quay"
}
]
}
]
}
}
You can call this endpoint in order to get up to date availability information for each product. The response contains the availability for each time slot of the requested product that falls within the supplied time period.
If there is no availability for a timeslot that falls within the range return an entry with 0 availability e.g. vacancies: 0. We do not return a NO_AVAILABILITY error code when no availability for a timeslot. If there is no availability at all in the given time range we return an empty array.
The parameter withProduct
is optional. If that is true, we return ticket detail in time slot level, including ticketPrice
, taxes
, ticketDescription
, ticketType
and so on.
For some products, we require customers select departure and destination before reserving tickets. In this case, the availability response contains options
which include departure and destination info.
The options must be provided when reserve and book tickets if the product is a departure/destination product.
Request sample
GET https://devshop.hornblower.com/api/gyg/1/get-availabilities/?productId=hbsdogco_53&fromDateTime=2019-11-01T00:00:00+02:00&toDateTime=2019-11-05T23:59:59+02:00&withProduct=false
Parameter | Type | Description |
---|---|---|
productId | String | The ID of the requested product in the hornblower’s system. |
fromDateTime | String | Marks the start (inclusive) of the requested time period (ISO 8601). |
toDateTime | String | Marks the end (inclusive) of the requested time period (ISO 8601). |
withProduct(Optional) | Boolean | The flag to return product detail. |
weight(Optional) | Int | Combined weight of the reservation. |
COUNT_XXX (Optional) | Int | Product count of the XXX type (ADULT, CHILD,INFANT, SENIOR), this is used for weight calculations if weight not provided. |
priceBand (Optional) | String | Price Band code |
withNetPrice (Optional) | String | The flag to return both retail rate and net rate |
Response sample
Fields | Type | Description |
---|---|---|
cutoffSeconds | Integer | An integer representing the cut-off time in seconds. |
vacancies | Integer | An interger representing available time slots |
ticketsData(Optional) | Array | An array contains ticket detail info. |
ticketsData[].taxes | Array | An array of tax object. |
taxes[].TaxPercentage | Integer | An percentage for the tax. |
taxes[].TaxIncluded | Boolean | The Boolean indicates if this tax is included or not. |
taxes[].TaxName | String | The name of the tax |
ticketsData[].ticketPrice | Float | The price base on our agreement. |
ticketsData[].ticketDescription | String | The description of ticket. |
ticketsData[].isTaxInclusive | Boolean | The Boolean indicates if the tax is inclusive or not. |
ticketsData[].category | String | The category we expect in booking call(ADULT , CHILD ,INFANT , SENIOR ). |
options(Optional) | Array | The array which contains optional options, such as fromStopId and toStopId |
options[].type | String | The option type |
options[].id | String | The option id |
options[].name | String | The option name |
Reservation
Make Reservation Example
const url = "https://devshop.hornblower.com/api/gyg/1/reserve";
const payload = {
data: {
bookingItems: [
{
category: "ADULT",
count: 2,
weight: 300
},
{
category: "CHILD",
count: 1,
weight: 50
},
],
dateTime: "2019-11-01T19:00:00-07:00",
productId: "hbsdogco_53",
priceBand: 'HB7',
options: [
{
type: "fromStopId",
id: 682,
name: "Swanage Pier"
},
{
type: "toStopId",
id: 683,
name: "Poole Quay"
}
]
},
};
const res = await fetch(url, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
body: JSON.stringify(payload),
method: "POST",
});
The above command returns JSON structured like this:
{
"data": {
"reservationReference": "hbsf_1C9FC6D3-5FFE-445E-837D-01EFCED94BB2"
}
}
This call represents the first step of the booking process. It reserves the requested quantities in the Hornblower system for 15 minutes in order to guarantee a successful booking within the following 15 minutes.
Failure to do so will potentially incur in unconfirmed bookings if the product is no longer available.
HTTP Request
POST https://devshop.hornblower.com/api/gyg/1/reserve
Parameter | Type | Description |
---|---|---|
productId | String | The ID of the requested product in the hornblower’s system. |
dateTime | String | The date/time of the activity to be reserved (ISO 8601). |
bookingItems | Array | A list specifying the number of persons per category in this reservation. |
priceBand (Optional) | Array | Price Band Code |
bookingItems[].category | String | The category for which the number of persons is being provided |
bookingItems[].count | Integer | The number of persons being reserved for the specified category. |
bookingItems[].weight (Optional) | Integer | Combined weight of the category, in property default units. |
options(Optional) | Array | The array which contains optional options, such as fromStopId and toStopId |
options[].type | String | The option type |
options[].id | String | The option id |
options[].name | String | The option name |
Reservation Amend
Amend Reservation Example
const url = "https://devshop.hornblower.com/api/gyg/1/amend";
const payload = {
data: {
anchorOrderReference: 'hbnyco_22143468',
newTourEventId: 9287992,
contact: {
fullName: 'Jake Sully',
firstName: 'Jake',
lastName: 'Sully',
postalCode: '85694',
emailAddress: 'sully.jake@hornblower.com',
phoneNumber: '+91XXXXXXXXX',
country: 'USA'
},
unitItems:[
{
category: 'ADULT', count: 4
},
{
category: 'Child', count: 2
}
]
}
};
const res = await fetch(url, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
body: JSON.stringify(payload),
method: "POST",
});
The above command returns JSON structured like this:
{
"data": {
"anchorOrderReference": "hbnyco_22143468",
"message": "Operation Success!",
"tickets": [{
"category": "Adult",
"ticketCode": "BAR-C-445YT-4433HGYYIQR-RRTJNN",
"ticketCodeType": "QR_CODE",
"status": "active"
}],
"err": null
}
}
Or An Error like this:
{
"data": {
"err": "Bad Request"
}
}
This Call enables an Authenticated user to make Amendments to their Booking for Contact Details, Tickets Count & Also, Allows one to Transfer Booking to a Future date Depending on availability.
HTTP Request
POST https://devshop.hornblower.com/api/gyg/1/amend
Parameter | Type | Description |
---|---|---|
anchorOrderReference (Required) | String | A unique identifier for the order that needs to be amended. |
newTourEventId (Optional) | String | If this parameter is provided, it indicates that the user wants to transfer the booking to a future date. The value should be the ID of the new tour event. |
unitItems (Optional) | Array | An array containing details of the tickets to be amended with their desired quantities and IDs. |
unitItems[].category (Required) | String | The Category of the ticket (e.g., Adult). |
unitItems[].count (Required) | Integer | The desired quantity of the ticket. This number will replace the previous ticket count. |
contact (Optional) | Object | An object containing updated contact details if the user wants to modify them. |
contact.firstName (Optional) | String | Updated first name of the contact. |
contact.lastName (Optional) | String | Updated last name of the contact. |
contact.postalCode (Optional) | String | Updated postal code of the contact in string format. |
contact.emailAddress (Optional) | String | Updated email address of the contact. |
contact.phoneNumber (Optional) | String | Updated phone number of the contact with the country code in string format. |
contact.country (Optional) | String | Updated country of the contact in string format. |
Reservation Cancellation
Cancel Reservation Example:
const url = "https://devshop.hornblower.com/api/gyg/1/cancel-reservation";
const payload = {
data: {
reservationReference: "hbsf_EBB72E93-3C11-4EFF-8B75-20F868ECFCB2",
},
};
const res = await fetch(url, {
headers: Object.assign({
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
}),
body: JSON.stringify(payload),
method: "POST",
});
The above command returns JSON structured like this:
{
"data": {}
}
This endpoint is able to cancel the reservation. This happens by default after 15 minutes.
HTTP Request
POST https://devshop.hornblower.com/api/gyg/1/cancel-reservation
Parameter | Type | Description |
---|---|---|
reservationReference | String | The identifier for the reservation in the hornblower’s system as returned by the reserve call. |
Booking
Book Example:
const url = "https://devshop.hornblower.com/api/gyg/1/book";
const payload = {
data: {
bookingItems: [
{
category: "ADULT",
count: 2,
weight: 300
},
{
category: "CHILD",
count: 1,
weight: 50
},
],
comment: "Require wheelchair assistance.",
dateTime: "2019-11-01T19:00:00-07:00",
gygBookingReference: "yourBookingRef",
language: "en",
productId: "hbsdogco_53",
reservationReference: "hbsf_1C9FC6D3-5FFE-445E-837D-01EFCED94BB2",
travelerHotel: "Hotel Name, Street, City, Country",
priceBand: 'HB7',
travelers: [
{
email: "john@john-smith.com",
firstName: "John",
lastName: "Smith",
phoneNumber: "+49 030 1231231",
passengerWeight: 170
},
{
email: "tom@tom-smith.com",
firstName: "Tom",
lastName: "Smith",
phoneNumber: "+49 030 1231232",
passengerWeight: 130
},
{
email: "sara@sara-smith.com",
firstName: "Sara",
lastName: "Smith",
phoneNumber: "+49 030 1231233",
passengerWeight: 50
}
],
options: [
{
type: "fromStopId",
id: 682,
name: "Swanage Pier"
},
{
type: "toStopId",
id: 683,
name: "Poole Quay"
}
]
},
};
const res = await fetch(url, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
body: JSON.stringify(payload),
method: "POST",
});
The above command returns JSON structured like this:
{
"data": {
"bookingReference": "hbsdogco_2087644",
"tickets": [
{
"category": "ADULT",
"ticketCode": "0005201907231624562430132112",
"ticketCodeType": "QR_CODE"
},
{
"category": "ADULT",
"ticketCode": "0005201907231624562430124303",
"ticketCodeType": "QR_CODE"
},
{
"category": "CHILD",
"ticketCode": "0005201907231624562430370991",
"ticketCodeType": "QR_CODE"
}
]
}
}
This endpoint confirms a reservation that has been placed previously and provides additional information.
The gygBookingReference
can be the booking reference in your system in case you need to do some mapping in your system.
HTTP Request
POST https://devshop.hornblower.com/api/gyg/1/book
Parameter | Type | Description |
---|---|---|
productId | String | The ID of the requested product in the hornblower’s system. |
reservationReference | String | The reservation reference returned by the reserve endpoint. |
gygBookingReference | String | The reference of this booking in your system. |
dateTime | String | The date/time of the activity to be reserved (ISO 8601). |
bookingItems | Array | A list specifying the number of persons per category in this reservation. |
bookingItems[].category | String | The category for which the number of persons is being provided. |
bookingItems[].count | Integer | The number of persons being booked for the specified category. |
bookingItems[].weight (Optional) | Integer | Combined weight of the category, in property default units. |
language | String | ISO 639-1 code of the language in which the product shall be booked. |
travelers | Array | A list of objects containing information about the travelers of this booking. The number of elements does not necessarily match the total indicated by the bookingItems parameter and in fact will most often include only the lead traveler. (Except when weight per passenger is required) |
travelers[].firstName | String | First name of the traveler. |
travelers[].lastName | String | Last name of the traveler. |
travelers[].email | String | Email address of the traveler |
travelers[].phoneNumber | String | Phone number of the traveler |
travelers[].passengerWeight (Optional) | Integer | Weight of the traveler, in property default units. |
comment | String | Additional information provided by the traveler e.g. special requests |
priceBand (Optional) | String | Price Band code |
travelerHotel (Optional) | String | Pickup location if available in the following format: "Hotel Name, Street, City, Country" |
options(Optional) | Array | The array which contains optional options, such as fromStopId and toStopId |
options[].type | String | The option type |
options[].id | String | The option id |
options[].name | String | The option name |
Get Booking
Get Booking Example:
const apiUrl = "https://devshop.hornblower.com/api/gyg/1";
const getOrder = async ({ url }) =>
fetch(url, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
method: "GET",
});
// Get Booking by bookingReference
const bookingReference = "hbny_1195572";
const bookingRefUrl = `${apiUrl}/get-booking/?bookingReference=${bookingReference}`;
const Booking = await getOrder({ url: bookingRefUrl });
// Get Booking By gygBookingReference
const gygBookingReference = "yourBookingRef";
const gygBookingRefUrl = `${apiUrl}/get-booking/?gygBookingReference=${gygBookingReference}`;
const Booking = await getOrder({ url: gygBookingRefUrl });
The above command returns JSON structured like this:
{
"data": {
"bookingReference": "hbny_1195572",
"tickets": [
{
"category": "adult",
"ticketCode": "0005201910161959436300613951",
"ticketCodeType": "QR_CODE"
},
{
"category": "adult",
"ticketCode": "0005201910161959436300863733",
"ticketCodeType": "QR_CODE"
},
{
"category": "child",
"ticketCode": "0005201910161959436300270374",
"ticketCodeType": "QR_CODE"
}
],
"orderInfo": {
"gygBookingReference": "GYG01234500",
"dateTime": "2019-12-29T18:30:00-05:00",
"productId": "hbny_1000023",
"comment": "",
"travelers": [
{
"firstName": "John",
"lastName": "Smith",
"phoneNumber": "+49 030 1231231",
"email": "john@john-smith.com"
}
]
}
}
}
This endpoint retrieves the booking information and tickets from hornblower system.
You can get the Booking by bookingReference
or gygBookingReference
.
HTTP Request
Get Booking by bookingReference
GET https://devshop.hornblower.com/api/gyg/1/get-booking/?bookingReference=hbny_1195572
Get Booking by gygBookingReference
GET https://devshop.hornblower.com/api/gyg/1/get-booking/?gygBookingReference=yourBookingRef
Parameter | Type | Description |
---|---|---|
bookingReference | String | The identifier for the booking in the hornblower’s system as returned by the book call. |
gygBookingReference | String | The identifier for the booking reference in your system as you provide to hornblower. |
Booking Cancellation
Cancel Booking Example:
const url = "https://devshop.hornblower.com/api/gyg/1/cancel-booking";
const payload = {
data: {
bookingReference: "hbsf_1088812",
},
};
const res = await fetch(url, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
body: JSON.stringify(payload),
method: "POST",
});
The above command returns JSON structured like this:
{
"data": {}
}
This endpoint cancels the booking in hornblower system.
HTTP Request
POST https://devshop.hornblower.com/api/gyg/1/cancel-booking
Parameter | Type | Description |
---|---|---|
bookingReference | String | The identifier for the booking in the hornblower’s system as returned by the book call. |
Gift Card Balance
Get Gift Card Balance Example:
const url = "https://my.hornblower.com/api/gyg/1/get-gift-card-balance/?giftCardNumber=VLOIWTOC";
const res = await fetch(url, {
headers: Object.assign({
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
}),
method: "GET",
});
The above command returns JSON structured like this:
{
"data": {
"totalBalance": 4378.62,
"giftcardNo": "VLOIWTOC"
}
}
HTTP Request
Parameter | Type | Description |
---|---|---|
giftCardNumber | String | This is the gift card number we want to check balance. |
Change Gift Card Balance Example:
const url = "https://my.hornblower.com/api/gyg/1/change-gift-card-balance";
const payload = {
"data":
{
"amount": -100,
"giftCardNumber": "VLOIWTOC",
"referenceNumber": "test 123456"
}
};
const res = await fetch(url, {
headers: {
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
},
body: JSON.stringify(payload),
method: "POST",
});
The above command returns JSON structured like this:
{
"data": {
"message": "Add balance -100 to GC VLOIWTOC"
}
}
HTTP Request
Parameter | Type | Description |
---|---|---|
amount | Number | This is the amount of gift card you want to add or subtract. |
giftCardNumber | String | This is the gift card number we want to add or subtract balance. |
referenceNumber | String | You can add reference notes to each transaction. |
Membership
Get Membership Example:
const url = "https://my.hornblower.com/api/gyg/1/get-membership/?membershipId=000511872676&propertyId=tozoo";
const res = await fetch(url, {
headers: Object.assign({
accept: "*/*",
"content-type": "application/json",
Authorization: basicToken,
}),
method: "GET",
});
The above command returns JSON structured like this:
{
"data": {
"membership": {
"importMembershipEndDate": 1708577999,
"importMembershipStartDate": 1676869200,
"orderId": "tozooB16974594",
"membershipStatus": "Active",
"importMembershipId": "000511872676",
"uses": 0,
"email": "shedley74@hotmail.com",
"membershipId": "000511872676",
"goodThru": 1708577999,
"membershipRelateNumber": 2000438854,
"transactionNumber": 511872676,
"customerProfile": {
"numberOfChild": 4,
"numberOfAdult": 2,
"info": [
{
"firstName": "Jennifer Susan Hedley",
"mainUser": true
},
{
"firstName": "Chris Paterson"
}
],
"numberOfSenior": 0,
"numberOfStudent": 0
},
"transactionDate": "02/20/2023",
"primaryContact": "Susan Hedley",
"id": 15178034,
"membershipTypeId": 130926,
"phone": "416-668-1371",
"barcode": "C2B-MLWpiw2Y-UKU7B9w-4rttYru-6D3m2Vzd2",
"dataType": "membership",
"propertyId": "tozoo",
"membershipSince": 1676869200,
"importChild": "4",
"transactionAmount": 85.19,
"importAdult": "2",
"membershipValidDays": 365,
"parkingId": "239002",
"membershipType": "Family Membership (2 Adults + up to 4 Children ages 3-15) - 1 Year",
"bookingTypeIdForMembership": 1107221,
"renewDiscountCostrate": 24969,
"propertyName": "Toronto Zoo",
"hasParkingPass": true,
"productConfig": {
"128714": 2,
"128715": 0,
"128716": 4,
"130643": 1
},
"costRate": 22660,
"memberCostRateMeta": [
{
"memberCostRate": 22660,
"costRateName": "Members"
},
{
"renewDiscountCostrate": 24969,
"costRateName": "Renewal Tier 1 (15%)"
}
],
"isActive": true,
"coupons": []
}
}
}
HTTP Request
Parameter | Type | Description |
---|---|---|
membershipId | String | After the completion of the Membership purchase, the customer is auto-generated a virtual Membership ID card which is emailed to them with a link to their customer account (this includes their membership ID number). |
propertyId | String | Property Id is our client unique identity, like toronto zoo's property id is tozoo. |
GraphQl APIs
GRAPHQL https://devshop.hornblower.com/graphql
Membership Card
Parameter | Type | Description |
---|---|---|
String | Member's email address. | |
membershipId: | String | After the completion of the Membership purchase, the customer is auto-generated a virtual Membership ID card which is emailed to them with a link to their customer account (this includes their membership ID number) |
membershipStatus | String | Current Active Status for the Member. |
membershipType | String | There are membership tiers, which come with different benefits (i.e. Silver = 10% discount, Gold = 15% + extra benefits) |
membershipSince | Int | Days since Membership Active. |
membershipValidDays | Int | Days Until Membership expires. |
hasParkingPass | Boolean | During the membership purchase, the customer can add on a parking pass to park for free during that same membership valid date range. |
photoUrl | String | All adults on the membership are required to submit their photo which is stored on the Member profile and cannot be updated. |
Note If customer's email is not supplied, Property Id and membership Id are needed to be passed in together
query MembershipProfile {
fetchMembershipProfile(email: "member@hornblower.com", correlationId: "membership_fetch_public_api", membershipId: "123456789", propertyId: "hornblower") {
membershipCard(correlationId: "membership_fetch_public_api") {
membershipId
membershipStatus
}
membershipId
membershipSince
membershipType
membershipTypeId
membershipValidDays
email
hasParkingPass
photoUrl
}
}
Webhooks
Introduction
When integrating with Anchor, you may wish to receive data about events as they occur within the platform so you can take action within your own backend systems. Webhooks are intended to help with this goal.
To setup webhooks, visit the Anchor Dashboard's "Webhooks" section and register your API endpoint. Within the dashboard, you will also be able to select which anchor events you wish to subscribe to. Once your endpoint is registered and has subscribed to events, the registered endpoint will begin receiving POST requests from Anchor whenever a subscribed event occurs in the system.
Within the Anchor Webhooks Dashboard you can monitor webhook request logs and view payloads from each event.
Note: Anchor Webhooks are in the early stages of development. New events and features will be added regularly.
Event Types
Name | ID | Category | Description |
---|---|---|---|
Order Created | orderCreatedSuccessfully | Orders | Occurs when an order is successfully placed. |
Order Status Change | orderStatusChange | Orders | Occurs when an order's status changes (tentative, confirmed, cancelled). |
Order Transferred | orderTransferred | Orders | Occurs when an order is transferred from one tour event to another. |
Barcode Scanned | checkin | Orders | Occurs when a product barcode is scanned. Typically occurs when guests check in to their event. |
Event Status Changed | eventStatusChange | Schedule | Occurs when an event's status is updated (tentative, confirmed, cancelled). |
Event Payload
Event Structure Example
{
"id": "b4336eab-f8c4-4aaf-a50d-7af92286264c",
"auth": "8bd3fe7088322d46f5e829066596c2d321cfa1d398eec19f3f75a9173ea0768e",
"created": 1702605463,
"type": "orderCreatedSuccessfully",
"data": {
***
}
}
Parameter | Description |
---|---|
id | Request Id |
auth | Authentication string (see authentication section for more details) |
created | UNIX timestamp of webhook request creation time |
type | Event ID |
data | Event data |
Event Authentication
Within the webhook endpoint configuration, you are able to provide an optional setting, Secret Key
. If you choose to provide this key, each request to your endpoint will contain the auth
field. This is a sha256 encrypted hash which encodes the following string '${id}:${secretKey}'
; where id
is the request id which is included in the request and secretKey
is the setting value you provided to the endpoint configuration.
To validate the event is coming from Anchor, create a sha256 hash using the same values as described above and ensure that the hash provided to you in the request is the same as your generated hash.
Event Retries
Webhook event delivery might occasionally fail due to network glitches, downstream service unavailability, or other network issues. In these cases an automated retry mechanism exists to re-attempt delivery to the receiving API endpoint. After an initial failed delivery attempt, there will be 4 followup attempts dispatched with an increasing delay between each. Once a successful delivery occurs (receiving endpoint returns a 2xx response) or after 4 failed followup attempts, no more automatic retries will be dispatched. Users are able to manually trigger re-sends within the webhooks Anchor UI for both failed and successful events.
REST API Endpoints
Within Anchor's REST API there are endpoints designed to assist with webhook implementations. Please reach out to the support team for an API key to access these endpoints.
Failed Webhooks
// Failed Webhooks Response Example
const res = {
data: {
failedWebhookEvents: [
{
attemptNumber: 1,
webhookEventId: 'e75d69c9-b11b-4ae3-81a2-b6174012374a',
event: 'orderCreatedSuccessfully',
responseStatusText: 'ENOTFOUND',
timestamp: 1720637061,
responseStatus: 'ENOTFOUND',
retries: [{
responseStatusText: 'ENOTFOUND',
responseStatus: 'ENOTFOUND',
timestamp: 1727815598,
event: 'orderCreatedSuccessfully',
attemptNumber: 2
}],
request: {
id: 'e75d69c9-b11b-4ae3-81a2-b6174012374a',
apiVersion: 1,
auth: 'e9d0a261d5deba500ed0e24b5bec50d225a83aee2aaa00bb3abdeb133d35feae',
created: 1720637061,
type: 'orderCreatedSuccessfully',
data: {} // Object containing event payload
}
}
]
}
};
Endpoint to retrieve webhooks where all attempts failed to deliver to the api endpoint. Query will return the payload that was attempted to be sent to your API endpoint.
Endpoint: GET baseUrl/api/webhooks/${propertyId}/payloads/failedWebhooks/${webhookId}
Endpoint is paginated and follows REST API 206 conventions. On the final page of data, the response header x-NoMoreData will be present.
Supported Query Parameters
Parameter | Description |
---|---|
startUnix | start unix timestamp of query |
endUnix | end unix timestamp of query |
fields | comma separated fields to project. EX: created,request.data.orderId |
rangeStart | start index of request page |
Status
// Status Response Example
const res = {
data: {
webhooksStatus: [{
apiVersion: 1,
active: false,
endpoint: 'https://fake.test-endpoint.com/anchor-webhook',
webhookId: '43760319-8008-43d4-8a30-895c2baf2c41'
}]
}
};
GET Endpoint to validate status of configured webhooks within a specified property.
Endpoint: GET baseUrl/api/webhooks/util/status/${propertyId}
Setup Steps
Follow the below steps to begin receiving and handling events from Anchor. You are free to set up one endpoint which handles multiple Anchor events. Alternatively, you are able to set up multiple endpoints for each event and configure them separately within the dashboard.
1. Select events to monitor.
View documentation or browse the Webhooks Dashboard to learn about available events and select those that are appropriate for your use-case.
2. Develop endpoint(s) to receive post requests that contain event data.
Set up an HTTPS endpoint that can accept POST requests. See above documentation for details on the content of this request.
Prior to any complicated logic you wish to perform after an event, be sure to return a successful response (2XX
) to prevent timeouts. You may want to consider using asynchronous techniques for logic that doesn't have to be performed immediately after a request is received.
3. Configure your endpoint within the Anchor Dashboard.
After creating the handler within your own API, add your new endpoint within the Anchor webhooks dashboard. Note that your endpoint must be a publicly accessible HTTPS url.
Select your desired events and provide a secret key for event authentication. Once submitted, your endpoint will immediately begin receiving POST requests from Anchor for your subscribed events.
4. Monitor and validate events.
If you provided a secret key to your endpoint configuration, you will have auth
fields within the requests you receive to your endpoint. See the above Event Authentication section for details on this. It is recommended you validate the requests you receive to make sure you aren't taking action on events that were not sent from Anchor.
You will also be able to view logs of all requests to your endpoints within the Anchor webhooks dashboard which can be useful for debugging issues within your API.
Frequently Asked Questions
1. Where can I get the token? Is there an endpoint allowing me to get a token?
There is no endpoint to get the token for now.
MTAwMDMzODpqZnFscmpncmhndTNyaXdmZThm
is the dev token, you can use it when developing.
2. Where can I get the list of productIds?
We don’t have all the products in our dev environment. You can always use hbsdogco_53
for testing.
When you are ready to move on to production, we can provide product mapping information for you.
On prod, you can get the list of active productId by calling Tours endpoint.
3. How long does it take for a reservation to expire?
The reservation will expire in 15 minutes.
4. Why do I get the NO_AVAILABILITY
error code when reserving the product?
We always return the tour local time with its timezone as UTC offset no matter where you are. Therefore, you need to exactly use what we return to reserve or book.
5. Can I reserve multiple products at the same time?
We only support one product per reservation. You have to reserve each product one by one if you want to reserve multiple products.
6. If there is any way to refine a search not only by passing in a date but also a time range?
We don't support the time range, we will return all available tours with correct times on that DATE. However, you have to use the correct date and time to reserve and book the tours.
7. How can I get the production credential?
We will issue the production credential only if you are able to book and cancel products successfully. Please please provide both canceled and valid bookingReferences to us. If everything looks good in our system, we will provide a production token to you.
Errors
When an error occurs, Hornblower API categorizes errors into one of the types listed in the table below in order to facilitate error handling and debugging. Additionally, an error message may be added to provide more detailed information.
A successful api call should always return a response object with a data field, which holds all the specified response data. In case of an error, two fields named errorCode and e rrorMessage should be set accordingly in the response instead.
{
"errorCode": "VALIDATION_FAILURE",
"errorMessage": "unknown category"
}
Error Code | Meaning |
---|---|
AUTHORIZATION_FAILURE | The provided credentials are not valid. |
NO_AVAILABILITY | The reservation or booking call cannot be fulfilled because there is insufficient availability. NO_AVAILABILITY must be use while handling a request for /reserve and /book only. |
INVALID_PRODUCT | The specified product does not exist or is broken for another reason (excluding availability issues). |
INVALID_RESERVATION | The specified reservation does not exist or is not in a valid state for the requested operation. |
INVALID_BOOKING | The specified booking does not exist or is not in a valid state. |
VALIDATION_FAILURE | The request object contains inconsistent or invalid data or is missing data. It must not be used for cases that are already covered by above errors. |
INVALID_TICKET_CATEGORY | The reservation or booking call specified a ticket category that is not configured for the requested product. This error requires an additional property in the JSON structure such as: |
INTERNAL_SYSTEM_FAILURE | An error occurred that is unexpected and/or doesn’t fit any of the types above. |