For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
DashboardGet an API key
Get StartedEventsToolsChangelog
Get StartedEventsToolsChangelog
  • Welcome
    • Overview
    • Quickstart
  • Concepts
    • Event envelope
    • Receiving webhooks
    • Signature verification
    • Retries and backoff
    • Testing
LogoLogo
DashboardGet an API key
Concepts

Receiving webhooks

|View as Markdown|Open in Claude|
Was this page helpful?
Previous

Event envelope

Next

Signature verification

Built with

Copy a handler, set PIVOTAL_WEBHOOK_SECRET from the endpoint’s signing secret, point Pivotal at the URL.

NODE / EXPRESS
webhook-handler.ts
1import express from "express";
2import { Webhook } from "svix";
3
4const app = express();
5const wh = new Webhook(process.env.PIVOTAL_WEBHOOK_SECRET!);
6
7app.post("/webhooks/pivotal", express.raw({ type: "application/json" }), (req, res) => {
8 try {
9 const evt = wh.verify(req.body, {
10 "svix-id": req.header("svix-id")!,
11 "svix-timestamp": req.header("svix-timestamp")!,
12 "svix-signature": req.header("svix-signature")!,
13 }) as { id: string; type: string; data: { object: any } };
14
15 // Idempotent: skip if you've already processed evt.id
16 handle(evt);
17 res.status(200).send("ok");
18 } catch {
19 res.status(401).send("invalid signature");
20 }
21});
PYTHON / FASTAPI
webhook_handler.py
1from fastapi import FastAPI, Request, HTTPException
2from svix.webhooks import Webhook, WebhookVerificationError
3import os
4
5app = FastAPI()
6wh = Webhook(os.environ["PIVOTAL_WEBHOOK_SECRET"])
7
8@app.post("/webhooks/pivotal")
9async def receive(request: Request):
10 body = await request.body()
11 try:
12 evt = wh.verify(body, {
13 "svix-id": request.headers["svix-id"],
14 "svix-timestamp": request.headers["svix-timestamp"],
15 "svix-signature": request.headers["svix-signature"],
16 })
17 except WebhookVerificationError:
18 raise HTTPException(401, "invalid signature")
19
20 handle(evt)
21 return "ok"
RULES
  • Return a 2xx within 10 seconds. Anything else triggers a retry.
  • Read the raw body before parsing JSON. Signature verification fails on re-stringified payloads.
  • Idempotency: every retry carries the same id. Store processed ids for at least 7 days.
  • Order is best-effort, not guaranteed. Prefer reading state from data.object over inferring from event order.