Skip to content

Webhook API

What is a webhook?

A webhook is a lightweight, event-driven communication that automatically sends data between applications via HTTP.

— https://www.redhat.com/en/topics/automation/what-is-a-webhook

What problem do they solve?

Configure webhooks to receive notifications via HTTPS when certain events occur within Keelvar's systems.

A simple way to explain the purpose of webhooks is to analogise it to push notifications on a mobile phone. Say you are expecting a message from a friend. You can either:

  • Every five minutes take your phone out of your pocket, unlock your phone, open your Messages app and check if you have received a message
  • Leave your phone in your pocket, wait for your phone to make a notification sound, unlock your phone and open your Messages app only when you know you have received a message

The first situation is equivalent to you constantly polling an endpoint on the internet and checking to see if the data it returns has been updated. The sooner you want to know when an update has occurred, the more frequently you need to poll that endpoint. This consumes needless compute resources for both the polling server and the polled server and in extreme cases can lead to system degradation or rate limits being imposed.

Creating a webhook

Contact support@keelvar.com and make a whitelist request for the domain of the downstream system that you want your configured webhook to send HTTPS notifications to. Also ensure you have a valid Keelvar API key.

Next, use the Webhook API as documented in the OpenAPI specification to create a new webhook. When creating a webhook you must choose at minimum:

  • a name for ease of identification
  • the URL of the receiving endpoint on the downstream system
    • only HTTPS protocol is supported
    • the receiving endpoint must be configured to accept POST requests
  • which event within Keelvar's system the webhook should subscribe to
    • e.g. Export API's event feed updated

Note: when a webhook is created successfully you will receive a JSON response which includes a field called signing_key.

The signing key is a secret

You must save signing_key to a secure location now as you will only be shown this secret once.

More info on the usage of signing_key is found in Using X-Signature to verify a webhook's authenticity

import requests
import json

api_key = "YOUR_API_KEY"
headers = {"Authorization": f"Bearer {api_key}"}
url = "https://my.keelvar.app/api/webhooks"

payload = {
    "name": "Basic Webhook",
    "description": "Demonstration of the Keelvar Webhook API",
    "signing_alg": "HMAC_SHA256",
    "signing_key": "46178bda07c9661d4f8f1b324be1426af516e0a30ffd51a88cd45e47c019cbab",
    "triggers": ["SOURCING_EVENTS_FEED_UPDATED"],
    "url": "https://test.url.ie",
}
response = requests.post(url, headers=headers, json=payload)

# Show the Webhook creation result
print(json.dumps(response.json())
#> {
#>    "description": "Demonstration of the Keelvar Webhook API.",
#>    "name": "Basic Webhook",
#>    "signing_alg": "HMAC_SHA256",
#>    "signing_key": "46178bda07c9661d4f8f1b324be1426af516e0a30ffd51a88cd45e47c019cbab",
#>    "triggers": "[SOURCING_EVENTS_FEED_UPDATED]",
#>    "url": "https://test.url.ie",
#>    "uuid": "4a55a230-307b-4bab-ac53-e95186b88968"
#> }
curl -X POST https://my.keelvar.app/api/webhooks \
    -H 'accept: application/json' \
    -H 'Content-Type: application/json' \
    -H 'Authorization: Bearer <YOUR_API_KEY>' \
    -d '{
          "name": "Basic Webhook",
          "description": "Demonstration of the Keelvar Webhook API",
          "signing_alg": "HMAC_SHA256",
          "signing_key": "46178bda07c9661d4f8f1b324be1426af516e0a30ffd51a88cd45e47c019cbab",
          "triggers": "[SOURCING_EVENTS_FEED_UPDATED]",
          "url": "https://test.url.ie",
        }'

#> {'description':'Demonstration of the Keelvar Webhook API.','name':'Basic Webhook','signing_alg':'HMAC_SHA256','signing_key': '46178bda07c9661d4f8f1b324be1426af516e0a30ffd51a88cd45e47c019cbab','triggers':'[SOURCING_EVENTS_FEED_UPDATED]','url':'https://test.url.ie','uuid':'4a55a230-307b-4bab-ac53-e95186b88968'}

Managing created webhooks

Facilities for retrieving the details of and deleting webhooks registered to your organisation are documented in the OpenAPI specification.

Examples

Get all details for a specific webhook.

import requests
import json

api_key = "YOUR_API_KEY"
headers = {"Authorization": f"Bearer {api_key}"}
url = "https://my.keelvar.app/api/webhooks/4a55a230-307b-4bab-ac53-e95186b88968"

response = requests.get(url, headers=headers)

# Show the Webhook GET result
print(json.dumps(response.json()))
#> {
#>    "description": "Demonstration of the Keelvar Webhook API.",
#>    "name": "Basic Webhook",
#>    "signing_alg": "HMAC_SHA256",
#>    "triggers": "[SOURCING_EVENTS_FEED_UPDATED]",
#>    "url": "https://test.url.ie",
#>    "uuid": "4a55a230-307b-4bab-ac53-e95186b88968"
#> }
curl -X GET https://my.keelvar.app/api/webhooks/4a55a230-307b-4bab-ac53-e95186b88968 \
    -H 'accept: application/json' \
    -H 'Authorization: Bearer <YOUR_API_KEY>' \

#> {'description':'Demonstration of the Keelvar Webhook API.','name':'Basic Webhook','signing_alg':'HMAC_SHA256','triggers':'[SOURCING_EVENTS_FEED_UPDATED]','url':'https://test.url.ie','uuid':'4a55a230-307b-4bab-ac53-e95186b88968'}

Delete a specific webhook.

import requests

api_key = "YOUR_API_KEY"
headers = {"Authorization": f"Bearer {api_key}"}
url = "https://my.keelvar.app/api/webhooks/4a55a230-307b-4bab-ac53-e95186b88968"

response = requests.delete(url, headers=headers)

# Show successful Webhook DELETE status code
print(response.status_code)
#>  204
curl -X DELETE https://my.keelvar.app/api/webhooks/4a55a230-307b-4bab-ac53-e95186b88968 \
    -H 'accept: application/json' \
    -H 'Authorization: Bearer <YOUR_API_KEY>' \

Webhook secret rotation

Webhook signing keys are secrets which are stored both by you and by Keelvar. The signing keys are stored securely by Keelvar and are only decrypted when signing a webhook notification.

Signing keys do not automatically expire and users of the Webhook API are responsible for securely storing their copy of the signing key. To rotate a signing key for a given webhook, users can:

  1. create a new webhook using the same settings as the webhook to be deleted
  2. swap your copy of the old signing key with your copy of the new signing key in your downstream system
  3. delete the webhook corresponding to this signing key using the Webhook API

Following these steps will result in no missed or duplicate notifications as your downstream system should be configured to only accept notifications from the:

  • old webhook prior to swapping the key
  • new webhook after swapping the key

Testing webhooks

You can manually schedule sending a notification for a given webhook as outlined in the OpenAPI specification.

Provided the domain of the webhook's url is whitelisted, you will shortly receive a test webhook notification at your configured url. This test notification's X-Signature header will be created using based on the test notification's request body which looks like the following:

{
  "trigger": "TEST",
  "timestamp": "2024-11-07T12:06:02.986232Z",
  "payload": {
    "detail": "Test"
  }
}

Example snippets for scheduling a test notification are as follow:

import requests
import json

api_key = "YOUR_API_KEY"
headers = {"Authorization": f"Bearer {api_key}"}
url = "https://my.keelvar.app/api/webhooks/4a55a230-307b-4bab-ac53-e95186b88968/test"

response = requests.post(url, headers=headers)

# Show the Webhook GET result
print(json.dumps(response.json()))
#> {
#>    "detail": "Webhook test notification to https://test.url.ie scheduled.",
#> }
curl -X POST https://my.keelvar.app/api/webhooks/4a55a230-307b-4bab-ac53-e95186b88968/test \
    -H 'accept: application/json' \
    -H 'Authorization: Bearer <YOUR_API_KEY>' \

#> {'detail':'Webhook test notification to https://test.url.ie scheduled.'}

Webhook security

Confidentiality

Keelvar will only send webhook requests over the HTTPS protocol to HTTPS enabled servers. Data contained in webhook notifications will be encrypted using your web server's public key prior to being sent over the public internet. Only your web server will be able to decrypt this data using its corresponding private key. Any third-party which intercepts this message in-transit will be unable to decipher its content.

Authenticity and integrity

Each webhook HTTPS request sent by Keelvar to your downstream systems will include a header called X-Signature. This header includes a Message Authentication Code which - in conjunction with a webhook's secret signing_key, signing_alg and the webhook's message content - can be used to verify that an incoming webhook was created by Keelvar and that the bytes content of that message have not been altered during transit.

Using X-Signature to verify a webhook's authenticity

The following Python code snippet creates a signature using hmac-sha256 algorithm, a HTTP request body and a secret which is known only to you and Keelvar (generated at time of webhook creation). Equivalent implementations in other programming languages should align closely with this snippet using those languages' respective standard cryptographic libraries.

Currently only the hmac-sha256 signing algorithm is supported by the Webhook API for creating Message Authentication Codes, but other algorithms may be added in the future.

import hashlib
import hmac

# Sample data for a request received by your downstream system
request_body = '{"triggers":"[LATEST_BIDS_FEED_UPDATED]","timestamp":"2021-09-11T03:43:37.151935Z","payload":{"sourcing_request":"310a8662-1bad-42ef-94cd-eeaf50f254fc","sourcing_event_name":"Test Event"}}'
request_X_Signature_header = "hmac-sha256=cbece13eb06622b3b032568f24d6c4aea11ed5e2cb40823ecf0723404a123f12"

# The secret singing_key generated when the webhook was first created
shared_secret_signing_key="d6d20aeae3e567a77bb43646115f32493c3edf8a0c1ad4de9ffa496a43edac3e"

# Recreate the signature using your copy of the secret signing_key and the request body
buffer = bytes(request_body, "utf-8")
signature = hmac.new(shared_secret_signing_key.encode("utf-8"), msg=buffer, digestmod=hashlib.sha256).hexdigest()

# Verify the signature you calculated matches the signature in the request header
assert f"hmac-sha256={signature}" == request_headers.get("X-Signature")

Dates, Times and Timezones

All dates and times throughout the entire API for both input and output are ISO 8601 standard and use UTC as the timezone. The following examples highlighting 1 second before midnight:

Zero offset UTC timezone
2030-12-31T23:59:59Z
2030-12-31T23:59:59+00:00
2030-12-31T23:59:59.000000Z