Webhook
Webhooks allow an external server to get notified when certain events happen.
Prerequisites
- A server reachable from the Internet (through public IP or port forwarding)
- Ability to write (or whip your coding agent to write) server-side programs that work with our schema
Schema
All webhooks are sent as POST requests with JSON body. The body schema is as follows:
typescript
{
imageID: number,
text: string,
rating: 'violent' | 'moderate' | 'none',
tags: string[],
imageURL: string,
createdAt: string, // ISO 8601
}The invocation request is as follows:
http
POST https://example.com/webhook/endpoint
Content-Type: application/json
X-Signature: 0123456890abcdef
{
"imageID": 2,
...
}Verification
In order to prove identity and integrity, webhooks are signed with the secret registered at creation. The signature is a hexadecimal string generated by signing the request body (in raw bytes encoded with UTF-8) with the webhook secret (also encoded with UTF-8) using HMAC-256, and will be sent in the X-Signature HTTP header.
To verify the signature, use whatever cryptography library you like. For example:
py
import hashlib
import hmac
import os
from flask import Flask, request, jsonify
app = Flask()
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"].encode("utf-8")
@app.post("/hooks/longhub")
def receive_webhook():
raw_body = request.get_data(cache=False)
received_signature = request.headers.get("X-Signature", "")
expected_signature = hmac.new(
WEBHOOK_SECRET,
raw_body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(received_signature, expected_signature):
return jsonify(error="invalid signature"), 401
payload = request.get_json()
print("Verified webhook:", payload)
return "", 204javascript
const express = require("express");
const crypto = require("crypto");
const app = express();
const WEBHOOK_SECRET = Buffer.from(process.env.WEBHOOK_SECRET, "utf8");
app.post("/hooks/longhub",
express.raw({ type: "*/*" }),
(req, res) => {
const rawBody = req.body;
const receivedSignature = req.get("X-Signature") || "";
const expectedSignature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const received = Buffer.from(receivedSignature, "utf8");
const expected = Buffer.from(expectedSignature, "utf8");
const valid =
received.length === expected.length &&
crypto.timingSafeEqual(received, expected);
if (!valid) {
return res.status(401).json({ error: "invalid signature" });
}
const payload = JSON.parse(rawBody.toString("utf8"));
console.log("Verified webhook:", payload);
return res.sendStatus(204);
}
);
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});Restrictions
To protect our server from being affected by malicious invocations, we have applied the following restrictions to webhooks:
- A user can have up to 10 webhooks.
- Webhooks are invoked via a remote Worker. You may see an IP from Cloudflare.
- Webhooks do not follow redirects, and redirect responses (HTTP 3xx) are considered failures.
- Webhooks that fail (HTTP 3xx, 4xx, 5xx) for 3 invocations in a row will be suspended until manual re-activation.
- Webhook invocations have a timeout of 30s.