Webhooks: Payloads, Signatures & Retries
Webhooks: Payloads, Signatures & Retries
GoFastSUPPORT.ai — Developer documentation
Enterprise workspaces can register HTTPS endpoints that receive signed, real-time POST notifications when tickets and chat conversations change. This page describes the events we send, the request format, how to verify the signature, and how retries work.
1. Events
An endpoint receives only the events it subscribed to. The available events are:
Event | Sent when |
|---|---|
| A new support ticket is created (any channel). |
| A ticket's status, priority, or details change. |
| A ticket is marked resolved. |
| A ticket is assigned to a staff member. |
| An AI chat conversation is handed off to a human. |
| A chat conversation is ended. |
2. Request format
Every delivery is an HTTPS POST with a JSON body and these headers:
Header | Meaning |
|---|---|
|
|
| The event name, e.g. |
| A unique delivery ID. Use it to deduplicate — retries reuse the same ID. |
| HMAC-SHA256 signature of the raw body: |
| Starts with |
The body always has this shape:
{
"event": "ticket.created",
"deliveryId": "8f4c2a1e-...",
"timestamp": "2026-07-15T12:34:56.000Z",
"tenantId": 42,
"data": { /* event-specific object, e.g. the ticket */ }
}Treat data as an open object: we may add fields over time, so parse what you need and ignore unknown keys.
3. Verifying the signature
When you create an endpoint, we show its signing secret (starting with whsec_) exactly once. Store it as a secret in your environment. On every delivery, compute an HMAC-SHA256 of the raw, unmodified request body with that secret and compare it to the X-GoFast-Signature-256 header using a constant-time comparison. Reject anything that does not match.
Important: sign the raw body bytes exactly as received. If your framework parses JSON before you can read the raw body (for example Express's express.json()), the re-serialized body may differ and verification will fail — capture the raw body first.
Node.js (Express)
const crypto = require("node:crypto");
const express = require("express");
const app = express();
function verifyGoFastSignature(secret, rawBody, signatureHeader) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
const a = Buffer.from(expected, "utf8");
const b = Buffer.from(signatureHeader || "", "utf8");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Use express.raw so the body is NOT parsed before verification.
app.post(
"/webhooks/gofast",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.get("X-GoFast-Signature-256");
const rawBody = req.body.toString("utf8");
if (
!verifyGoFastSignature(
process.env.GOFAST_WEBHOOK_SECRET,
rawBody,
signature,
)
) {
return res.status(401).send("invalid signature");
}
const payload = JSON.parse(rawBody);
// Respond quickly; do heavy work asynchronously.
res.sendStatus(200);
console.log("Received", payload.event, payload.deliveryId);
},
);
Python (Flask)
import hashlib
import hmac
import os
from flask import Flask, request
app = Flask(__name__)
def verify_gofast_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode("utf-8"), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header or "")
@app.post("/webhooks/gofast")
def gofast_webhook():
signature = request.headers.get("X-GoFast-Signature-256", "")
if not verify_gofast_signature(
os.environ["GOFAST_WEBHOOK_SECRET"], request.get_data(), signature
):
return "invalid signature", 401
payload = request.get_json(force=True)
# Respond quickly; do heavy work asynchronously.
return "", 200
4. Responding, retries & auto-disable
Respond with any 2xx status within 10 seconds. Anything else (including redirects and timeouts) counts as a failure.
Failed deliveries are retried up to 3 more times: after 5 seconds, 30 seconds, and 5 minutes.
If a delivery exhausts all attempts, the endpoint is automatically disabled and your workspace admins receive an email. Re-enable it from the Webhooks page once your endpoint is healthy; you can also redeliver recent events from the delivery log.
Deliveries may occasionally arrive more than once or out of order. Use
X-GoFast-Delivery(alsodeliveryIdin the body) to deduplicate, and thetimestampfield to detect stale data.
5. Endpoint requirements & best practices
Endpoints must be public HTTPS URLs — private or internal network addresses are rejected.
Always verify the signature before trusting a request. Never process unsigned or mismatched payloads.
Keep your signing secret out of source control. If it leaks, delete the endpoint and create a new one to rotate the secret.
Acknowledge fast (return 200 immediately) and queue heavy processing so you never hit the 10-second timeout.
Webhooks are managed from your dashboard under Webhooks. If you run into trouble, contact support with the delivery ID from the delivery log.