# Receiving Webhooks

## Overview

In this guide, you'll learn how to set up and handle webhooks in your Duffel integration.

After you've set up webhooks, you'll receive notifications about events that happen in your Duffel account - for example, when an airline has a schedule change affecting one of your orders.

We'll send these events to your server, and then you can process them and take action automatically - for example updating your database or emailing a customer.

We'll go through three simple steps to set this up:

- Build a simple webhook receiver in Python
- Create a webhook
- Send a test event to our webhook

## Building a simple webhook receiver in Python

Let's start by creating a server, running locally on our machine, that can receive event notifications.

For this example, we'll write our server in Python 3, but you can use any modern programming language.

We'll assume that you have Python 3 and [`pip`](https://pypi.org/project/pip/) installed. We'll use [Flask](https://flask.palletsprojects.com/en/1.1.x/) to quickly build a server. You can install Flask with `pip install flask`.

Once you have Flask installed, copy the code below and paste it into an `app.py` file. After you've set up your webhook in step 2, we'll replace `<your-generated-webhook-secret>` with your webhook's `secret`.

```
import json
import hmac
import hashlib
import base64
from flask import Flask, jsonify, request

app = Flask(__name__)

# Secret as bytes, ready for comparison
secret = b"<your-generated-webhook-secret>"

# Our route that will receive the webhooks from Duffel's servers
@app.route('/webhooks', methods=['POST'])
def hello_world():
    if not compare(secret, request):
        print('⚠️  Unsafe payload')
        return jsonify(success=True)
    event = None
    try:
        event = request.json
        print('ℹ️ Parsed event')
    except:
        print('⚠️  Webhook error while parsing basic request.' + str(e))
        return jsonify(success=False)
    if event and event['type'] in ['order.updated', 'order.airline_initiated_change_detected', 'ping.triggered']:
        print('ℹ️ Event type: ' + event['type'])
    else:
        print('⚠️ Unhandled event type {}'.format(event['type']))
        return jsonify(success=True)
    print('✅Handled event!')
    return jsonify(success=True)

def compare(secret, request):
    raw_payload = request.get_data()
    raw_signature = request.headers['X-Duffel-Signature']
    pairs = list(map(lambda x: x.split('='), raw_signature.split(',')))
    t = pairs[0][1]
v1 = pairs[1][1]
    local_signature = signature(secret, raw_payload, t)
    return hmac.compare_digest(v1, local_signature)

def signature(secret, payload, timestamp):
    signed_payload = timestamp.encode() + b"." + payload
    signature = hmac.new(secret, signed_payload, hashlib.sha256).digest()
    return base64.b16encode(signature).lower().decode()
```

The application is very simple. The comments throughout explain the flow of how we process a received notification.

You can run this application on port 4567 with a simple command:

```
FLASK_ENV=development FLASK_RUN_PORT=4567 FLASK_APP=app.py flask run
```

You'll need to expose your application to the internet to be able to receive events from Duffel. The simplest way to do this on your local machine is to use [`ngrok`](https://ngrok.com/download), which is free to download across all major operating systems.

Once you've installed `ngrok`, you can run it from the command line and expose port 4567 to the internet with the following command:

```
ngrok http 4567
```

With `ngrok` running, you'll need to copy the URL it gives you so you can pass that to Duffel and we can send you events.

## Creating a webhook

Next, we need to use the Duffel API to [create a new webhook](/content/docs/api/webhooks/create-webhook/index.html) pointing to our webhook receiver.

Duffel can send a range of different kinds of "event" via a webhook. Let's start by creating a webhook that is subscribed to the `order.airline_initiated_change_detected` event. This event will be triggered, and we'll send a request to your webhook, when an order has a schedule change initiated by the airline.

You can use a request like this to create a webhook - you'll need to replace `<YOUR_ACCESS_TOKEN>` with your access token for the Duffel API, and the `url` with the URL you got from `ngrok` above:

```
curl -X POST --compressed "https://api.duffel.com/air/webhooks" \
  -H "Accept-Encoding: gzip" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Duffel-Version: v2" \
  -H "Authorization: Bearer $YOUR_ACCESS_TOKEN" \
  -d '{
  "data": {
    "url": "https://www.example.com:4000/webhooks",
    "events": [
      "order.airline_initiated_change_detected"
    ]
  }
}'
```

You'll get back a response like this:

```
{
  "meta": null,
  "data": {
    "url": "http://be3baxdc.ngrok.io/webhooks",
    "secret": "54vFWvaSbbzYpxXPeB4YEw==",
    "id": "end_0000A5TK3psWyzKIU2La52",
    "live_mode": false,
    "events": ["order.airline_initiated_change_detected"],
    "created_at": "2021-03-22T15:14:42.127734Z",
    "active": true
  }
}
```

You'll see that the webhook is currently `active`. You can deactivate a webhook using the [update a webhook](/content/docs/api/webhooks/update-webhook/index.html) endpoint in the API; you can also change the URL if you wish with this endpoint.

You must write down the returned `secret` as it's only available at the time when you create a webhook. You'll never be able to see it again.

You'll need to take that secret and replace `<your-generated-webhook-secret>` in your Python code above with it, and then restart the application.

## Sending a test event to our webhook

We now have our local webhook receiver running and we've created a webhook in Duffel to send events to that receiver. Congratulations! 🎉 The next step is to make sure it works.

We have an API endpoint that allows you to [trigger a ping event](/content/docs/api/webhooks/ping-webhook/index.html) to your webhook. To trigger a ping, you'll need the ID for the webhook (which was returned when you created your webhook - for example `sev_0000A5TK3psWyzKIU2La52`). With that, you can send a request to Duffel to trigger the ping event:

```
curl -X POST --compressed "https://api.duffel.com/air/webhooks/id/$WEBHOOK_ID/actions/ping" \
  -H "Accept-Encoding: gzip" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Duffel-Version: v2" \
  -H "Authorization: Bearer $YOUR_ACCESS_TOKEN" \
  -d '{}'
```

If your webhook was configured successfully and your code was running and accessible to the internet, your local Python server should output `✅ Handled event!`.

## What's next?

You've created a webhook which subscribes to the `order.airline_initiated_change_detected` event, so you'd receive a push whenever an airline changes the schedule of your order.

An `order.airline_initiated_change_detected` webhook looks like this:

```
{
  "created_at": "2021-04-22T13:13:18.420272Z",
  "data": {
    "object": {
      ..
    }
  },
  "id": "wev_0000A6VP9fgKxAccTSKWUy",
  "live_mode": false,
  "object": "order",
  "type": "order.airline_initiated_change_detected",
  "idempotency_key": "aic_0000ApoiwggSbt7BordU1o"
}
```

In your code, you should look at the `type` of events that you receive and handle that appropriately. As our system will retry failed events, there can be cases when we will send you duplicate events, in those cases you should use the `idempotency_key` to check for uniqueness.
