Webhooks

A webhook is a mechanism for receiving updates about resources without periodically requesting the resources (known as polling). This solves important real-time issues, like having the most recent availability and prices to display to your customers and avoids unnecessarily consuming server resources.

To support webhooks, the developer must register an endpoint (URL) with the Application. This URL will be sent POST requests from the G Adventures API. Webhooks can be registered by editing the Application and updating the webhooks settings.

Note

Currently a webhook registration is for ALL event types (i.e. all or nothing approach). If you would like specific subscriptions, please let us know.

Common Uses

A webhook is fired whenever the representation of a resource changes. Thus, if a departures resource changes, it will not necessarily deliver the “parent” resource, tour_dossiers unless the change in that departure caused a change in the tour_dossiers resource.

Additionally, below is a table containing common business needs, and the collection of webhooks you’d want to consider. Be aware that this is a subset and is only meant to provide an example of how you’d approach capturing webhooks.

Below, a table containing a realistic use-case, along with the webhooks you’d ideally listen to.

Use Cases

Webhooks Collection

Displaying a tour, its itineraries & dates. The price shown is a single “lead” price and no specific dates and availability are displayed.

tour_dossiers, itineraries

Displaying a tour/itinerary as above, but includes departure dates including availability, pricing per departure

tour_dossiers, itineraries, departures

As above, but includes promotional pricing

tour_dossiers, itineraries, departures (All Promotion data is displayed in the departure)

Collecting customer data and displaying it in reports, booking systems, etc.

customers

Collecting any booking data attributed to your agency

customers, bookings, departure_services (And all other *_services as you need), payments

Creating a form for your customers to checkout and purchase a G Adventures Trip

countries, states, nationalities (Form meta data)

Common Webhook Patterns

Receiving a Webhook

Configuring your server to receive a new webhook is no different than creating any other page on your site. With most frameworks, you’d add a new route with the desired URL and parse the request’s data.

Webhook data is sent as JSON in the request’s body. We advise you to guard against replay-attacks by recording which events you receive and never processing them twice.

An example webhook view using the popular Flask framework for Python might look something like this:

@application.route('/webhooks', methods=['POST'])
def webhooks_receiver():
   # .. do something with request.json
   return "OK", 200

Registering a Webhook

When editing your Application, you have the option to enter a Webhook URL. In order to verify the URL, you must respond to the POST request accordingly.

First, the response from the URL must be of 200 OK status code. Next, you must include a header named X-Application-SHA256 containing the SHA256 hex digest of your live application key. To get this value, you can simply visit your application dashboard, or compute it using your language’s hash functions.

When entering a new webhook, you have the option to pick your preferred representation format. All webhooks to this callback will be sent to the selected format.

Responding to a Webhook

Your application, if it successfully handles the POST, must reply with a 200 OK status code. If it does not, the post will be retried for a maximum of 72 hours before the job is abandoned.

Verifying a Webhook

Webhooks delivered by the G API can be verified by inspecting the X-Gapi-Signature that is part of each web hook request. To verify the request came from the G API, compute the HMAC digest according to the following algorithm and compare it to the value in the X-Gapi-Signature header. If they match, you can be sure the web hook came from the G API.

Here’s an example of how one might do that in Python:

import hashlib
import hmac
from flask import request

APPLICATION_KEY = 'MY_SECRET_APPLICATION_KEY'

@application.route('/webhooks', methods=['POST'])
def webhooks_receiver():

   data = request.get_data()
   calculated_hmac = hmac.new(APPLICATION_KEY, data, hashlib.sha256).hexdigest()

   if calculated_hmac == request.headers.get('X-Gapi-Signature'):
       return "OK", 200

   # Signatures did not match, raise an exception or don't use the web hook..

Alternatively, we have also implemented the above as a helper method inside our gapipy project which can be used as follows,

from flask import request
from gapipy.utils import compute_request_signature

APPLICATION_KEY = 'MY_SECRET_APPLICATION_KEY'

@application.route("/webhook", methods=["POST"])
def webhooks_receiver():

    data = request.get_json(force=True)
    calculated_hmac = compute_request_signature(APPLICATION_KEY, request.data)

    if calculated_hmac == request.headers.get("X-Gapi-Signature"):
        return "OK", 200

    # Signatures did not match, raise an exception or don't use the webhook

Most languages and frameworks you work with should have the ability to compute an HMAC using the sha256 algorithm. This process is of course optional but can help you sleep better at night in ensuring no unverified requests are making it to your web hook receiver.

Events

The POST containing:

  • event_type – a string describing the event ({resource}.updates, {resource}.deleted).

  • resource - identifies the resource that triggered the event

  • created - the datetime of when the event was created. (See Dates & Times)

  • data - a collection of data, containing the unique reference (id) and URL (href) to the specific object of concern.

The event_type string is for identifying what operation has occurred on a resource and has a consistent pattern across all resources, RESOURCE_TYPE.EVENT_NAME. The resource matches the existing resources available in the API and the event name can be either updated, or deleted.

We used to provide created event types, but this has been discontinued since March 2017. You can safely use the updated event to capture any scenario where new, or existing data is relevant to you.

Webhook events contain a list of items since many actions affect multiple resources. For example, adding (CREATE) a departure service to a booking will cause a Webhook event with the following list of items:

  • departure_services.updated

  • bookings.updated (e.g. booking.amount_owing is increased, as well as other fields).

  • invoices.updated (new invoice for every action that affects invoice amounts)

  • documents.updated (there is a new document for every invoice)

Cancelling that same departure service would have a similar Webhook event generated, with multiple items.

XML Schema: events.xsd

Note

For security and privacy, the full object is not included in the body. The id and href references allow you to retrieve the resource securely from the API using your API Key. To secure your endpoint, only retrieve data from rest.gadventures.com.

Example Event Body

[
    {
      "event_type": "departures_services.updated",
      "resource": "departure_services",
      "created": "2013-05-17T05:34:38Z",
      "data": {
          "id": "123",
          "href": "|live_url|/departure_services/123"
      }
    },
    {
      "event_type": "bookings.updated",
      "resource": "bookings",
      "created": "2013-05-17T05:34:38Z",
      "data": {
          "id": "123",
          "href": "|live_url|/bookings/123"
      }
    },
    {
      "event_type": "invoices.updated",
      "resource": "invoices",
      "created": "2013-05-17T05:34:38Z",
      "data": {
          "id": "123",
          "href": "|live_url|/invoices/123"
      }
    },
    {
      "event_type": "documents.updated",
      "resource": "documents",
      "created": "2013-05-17T05:34:38Z",
      "data": {
          "id": "123",
          "href": "|live_url|/documents/123"
      }
    }
]

Example XML Event Body

<events>
    <event>
        <event_type>departure_services.updated</event_type>
        <resource>departure_services</resource>
        <created>2013-05-17T05:34:38Z</created>
        <data>
            <id>123</id>
            <href>https://rest.gadventures.com/departure_services/123</href>
        </data>
    </event>
    <event>
        <event_type>bookings.updated</event_type>
        <resource>bookings</resource>
        <created>2013-05-17T05:34:38Z</created>
        <data>
            <id>123</id>
            <href>https://rest.gadventures.com/bookings/123</href>
        </data>
    </event>
    <event>
        <event_type>invoices.updated</event_type>
        <resource>invoices</resource>
        <created>2013-05-17T05:34:38Z</created>
        <data>
            <id>123</id>
            <href>https://rest.gadventures.com/invoices/123</href>
        </data>
    </event>
    <event>
        <event_type>documents.updated</event_type>
        <resource>documents</resource>
        <created>2013-05-17T05:34:38Z</created>
        <data>
            <id>123</id>
            <href>https://rest.gadventures.com/documents/123</href>
        </data>
    </event>
</events>

Best Practices

Initial Load & Lifecycle

We recommend using Webhooks with the following process to keep a local copy of API data:

  • Write a script to iterate through the list view of a resource, following all links to other resources.

  • Store a copy of each relevant resource in a local database, cache, etc…

  • Use Webhook events as a notification to create, update, or delete your local copy of each resource.

  • Webhook events can also be used to trigger internal processes, for example:
    • A change in value for a particular resource field may trigger the execution of a piece of code.

    • Sending an email.

    • Adding a task to a queue.

  • Repeat for each relevant resource list view.

Following this best practice has the following benefits:

  • The script to iterate through all resources only needs to be executed once.

  • No need for polling of updates to resources, minimizing calls to the API and maximizing internal efficiency.

  • Real-time updates and synchronization of your website, backend systems, etc…

System Architecture

We DON’T rate limit outgoing Webhook POSTs. This means that it is important that your receiver add all incoming requests to a queue to be processed (i.e. make a subsequent API call the the resource/ID identified in the Webhook) according to your own system limits. If you try to process all Webhook events immediately upon receiving them, this could lead to problems.

For example, at certain times of the year we bulk update all of our departure pricing. This bulk operation causes thousands (if not tens of thousands) of Webhook events to be generated in a very short period of time. Trying to process all of these Webhook events in real-time will likely cause problems, leading to your server to return a 500 or 504 error code.

You will also want to have your Webhook queue processing integrated with whatever throttling mechanism you are using to avoid hitting our Caching. This is another good reason for using a queue to process Webhook events.

Disabling Webhooks

If we receive 500 non 200 responses to outgoing Webhook events in a one hour time frame, we will send a warning email to the email address associated with that API account. If we receive an additional 250 non 200 responses to outgoing Webhook events in that same one hour time frame, we will disable the Webhooks for that API application. An automated email will be sent to the email address associated with that API account, indicating that we have disabled the Webhook for that application. You will need to email support to have the Webhook re-enabled.

Testing Webhooks

The best ways to test webhooks:

  • Webhooks Playground

  • In the Dashboard, monitoring the last 500 request/response events that were sent to your application Webhook URL

  • Writing to booking-related resources, and capturing the resulting webhooks which are fired through your changes.

Webhook Playground

Using the Webhooks Playground is the best way to first test your server’s webhook listener to ensure you are receiving the event and correctly processing the data contained within the body of the webhook. These tests only contain a single item in the webhook body list of events.

Writing to resources

Testing of booking-related resources can be done via WRITE actions using your test api key. A CREATE or UPDATE action on a resource will cause a Webhook to be fired with events for one or more resources. This is a good way to test making bookings, cancelling services, updating customers, etc…