NAV
javascript
Postman Collection

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
email 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.