Webhooks

Webhooks allow you to push information from Administrate's platform to an external system, based on triggering events such as record creation and updates. This is useful for integrations that require near-real-time notifications and avoids you from having to periodically poll for updates using our APIs.

For a deep dive into how to discover which events are enabled for Webhooks, and how to create a Webhook and filtered Webhooks see our Core Webhooks documentation

Request failures and reattempts

The HTTP status code returned by the request to the webhook's url determines if it will be retried:

  • server error (status code ≥ 500) or rate limiting errors (status code 429)
    • the request will be retried up to 3 times (after 30 seconds, 1 and 2 minutes)
    • if the last retry fails, the addresses on the webhook's notificationEmails will be notified
  • client error (status code ≥ 400 and < 500 excluding 429):
    • the request will not be retried
    • the addresses on the webhook's notificationEmails will be notified

Authentication

Webhooks are encrypted (delivered via TLS) to an endpoint specified during creation.

We currently recommend Basic Auth for authentication, but we also support shared secret authentication by providing a secret in the Webhook's config during setup.

When we send a payload to the endpoint, we include the config so you can validate that the request is coming from Administrate.

OAuth authentication is not well suited for one-way requests like Webhooks. For example, obtaining refresh tokens is not straightforward and introduces additional complexity and overhead when Basic Auth or a shared secret strategy works well.

For more information about setting up Webhooks, see our Core Webhooks documentation on how to set up a Webhook with an example of Basic Auth and shared secret authentication.

User Agent Header

The user-agent header for all webhooks is set to Administrate-Webhooks/1.0.

HMAC Verification

Our webhook payloads are signed via HMAC (Hash-based message authentication code). These signatures can be used by a consumer to verify the request is genuine and came from Administrate. In order to do this, we will provide you with the secret key (hexadecimal format) stored on our end.

All webhooks sent by our system include:

  • header X-Administrate-signature containing the HMAC hash in hexadecimal format
  • header X-Administrate-signature-timestamp containing the timestamp the request was sent (in seconds from the Unix Epoch)
  • field metadata.sent_at in the request body: the UTC timestamp the request was sent (in ISO 8601 format). This can be used to prevent replay attacks

The HMAC algorithm we use is SHA512 based and operates on the single spaced JSON (with unicode escaping) that is sent in the webhook payload. Ideally calculate the hash from the bytes received in the request body rather than parsing the JSON first.

The below examples show how a webhook request's HMAC signature can be verified on the receiving end.

Example (Python/Flask)

import hmac, os
from flask import Flask, request, jsonify

app = Flask(__name__)

SECRET = os.getenv("HMAC_SECRET")  # secret should be stored securely


@app.route("/test/python", methods=["POST"])
def test_route():
    received_digest = request.headers.get("X-Administrate-signature")
    hmac_object = hmac.new(bytes.fromhex(SECRET), request.data, "SHA512")
    generated_digest = hmac_object.hexdigest()
    if not hmac.compare_digest(received_digest, generated_digest):
        # Verification failed: abort
        return jsonify({"error": "Signature verification failed"}), 401

    # Verification succeeded: process request
    return jsonify({"success": True}), 200


if __name__ == "__main__":
    app.run()

Example (NodeJS/Express)

const { createHmac } = require("crypto");
const express = require("express");

const SECRET = process.env.HMAC_SECRET; // secret should be stored securely
const app = express();
app.use(express.text({ type: "*/*" }));

app.post("/test/nodejs", (req, res) => {
  const receivedSignature =
    req.headers["X-Administrate-signature".toLowerCase()];
  const generatedSignature = createHmac("sha512", Buffer.from(SECRET, "hex"))
    .update(req.body)
    .digest("hex");
  if (generatedSignature !== receivedSignature) {
    // Verification failed: abort
    res.status(401);
    return res.send({ error: "Signature verification failed" });
  }

  // Verification succeeded: process request

  res.status(200);
  return res.send({ success: true });
});

app.listen(8000, () => {});

Example (Java)

public class HmacUtil {
    public static String generateHMAC(String payload, String hexKey) throws Exception {
        String algorithm = "HmacSHA512";
        // Convert the hex key string to a byte array
        byte[] keyBytes = hexStringToByteArray(hexKey);

        // Create an HMAC instance with the specified algorithm
        Mac mac = Mac.getInstance(algorithm);
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, algorithm);
        mac.init(secretKeySpec);

        // Generate the HMAC for the payload (using UTF-8 encoding)
        byte[] hmacBytes = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));

        // Convert the resulting HMAC to a hexadecimal string
        return bytesToHex(hmacBytes);
    }

    private static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}

Best Practices

Prepare for Scale

Webhooks are sent when updates are performed in the Administrate platform. This can cause the volume of Webhooks sent to fluctuate during times of the day.

If a large number of updates are performed then prepare for a large number of Webhooks to be sent to your external system.

Check timestamps

A series of entity updates during a short time interval will result in the same Webhook firing multiple times. We do not guarantee that your external system will receive these Webhooks in the exact same order of those updates that were performed. The triggered_at timestamp in the payload's metadata indicates when the event was triggered:

{
    "metadata": {
        "triggered_at": "2023-07-19T09:05:13.000000Z",
        "webhook_id": "T3V0Ym91bmRIb29rOjg="
    },
    "payload": {}
}

Your system should ignore any webhooks with a timestamp that is earlier than those already processed.

Have separate apps for handling Webhooks

We recommend having a separate integration for handling Webhooks requests.

For example, if you are planning on using an Event Created Webhook to update an external website, do not have an endpoint on the website. This can prevent our Webhook system from potentially overwhelming the website.

Don't process Webhooks during requests

Our Webhooks system will send the generated payload to an endpoint on your external system. You should store the payload and perform any further action in another process, not during the HTTP request.

This will allow you to handle a large number of requests by taking a lot less time to process the Webhook and be more tolerant of errors if your external app is connecting to another system and it is having issues.