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:
-
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…