Recipe: verify a webhook
Never process a webhook without verifying x-scrapersocial-signature. The header is
t=<unix>,v1=<hex> where v1 is HMAC-SHA256 of "{t}.{raw_body}" using your
whsec_ secret.
Node (Express)
import crypto from "node:crypto";import express from "express";
const app = express();
app.post("/hooks/ss", express.raw({ type: "application/json" }), (req, res) => { const header = req.header("x-scrapersocial-signature") ?? ""; const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); const { t, v1 } = parts;
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) { return res.status(400).send("stale or malformed signature"); }
const expected = crypto .createHmac("sha256", process.env.SS_WEBHOOK_SECRET) .update(`${t}.${req.body}`) .digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) { return res.status(401).send("bad signature"); }
const event = JSON.parse(req.body); // handle event.type: job.completed | credits.low | plan.changed res.sendStatus(200);});Python (FastAPI)
import hashlib, hmac, json, os, timefrom fastapi import FastAPI, Header, HTTPException, Request
app = FastAPI()SECRET = os.environ["SS_WEBHOOK_SECRET"].encode()
@app.post("/hooks/ss")async def hook(request: Request, x_scrapersocial_signature: str = Header("")): raw = await request.body() parts = dict(p.split("=") for p in x_scrapersocial_signature.split(",") if "=" in p) t, v1 = parts.get("t", ""), parts.get("v1", "")
if not t or abs(time.time() - int(t)) > 300: raise HTTPException(400, "stale signature")
expected = hmac.new(SECRET, f"{t}.".encode() + raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, v1): raise HTTPException(401, "bad signature")
event = json.loads(raw) return {"ok": True}Checklist
- Compute over the raw body, before any JSON parsing or re-serialization.
- Use a constant-time comparison (
timingSafeEqual/compare_digest). - Reject timestamps older than ~5 minutes to block replays.
- Respond 2xx fast; do slow work async — deliveries retry on non-2xx.
Related: Webhooks guide · Webhooks API