Webhooks
Register webhook endpoints to receive real-time notifications when a pause changes state. This is the recommended alternative to polling.
Register a Webhook#
curl -X POST https://api.pausepoint.dev/v1/webhooks \
-H "Authorization: Bearer $PP_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/pausepoint-webhook",
"events": ["pause.responded", "pause.timed_out", "pause.cancelled"],
"secret": "your-webhook-signing-secret"
}'
Choose a random, high-entropy secret of at least 16 characters (e.g. openssl rand -hex 32). This secret signs every outbound webhook payload so you can verify authenticity. Webhook URLs must be https:// and publicly reachable; private and internal addresses are rejected.
Event Types#
| Event | Fires when |
|---|---|
pause.responded |
A human submits a response via the response page |
pause.timed_out |
The pause reaches expires_at without a response |
pause.cancelled |
A developer calls DELETE /v1/pause/{id} |
Payload Format#
All events share this envelope:
{
"event": "pause.responded",
"pause_id": "psr_uuid",
"developer_id": "dev_uuid",
"timestamp": "2026-05-08T14:23:00Z",
"data": {
"choice": "Approve",
"text": null,
"response_at": "2026-05-08T14:23:00Z",
"metadata": {"amount": 340, "recipient_name": "Acme Corp"}
}
}
For pause.timed_out:
{
"event": "pause.timed_out",
"pause_id": "psr_uuid",
"developer_id": "dev_uuid",
"timestamp": "2026-05-08T18:00:00Z",
"data": {
"choice": "Reject",
"response_at": "2026-05-08T18:00:00Z",
"metadata": {"amount": 340}
}
}
data.choice is null if no timeout_default was set.
Signature Verification#
Every outbound webhook includes:
X-PausePoint-Signature: sha256=<hex>
X-PausePoint-Event: pause.responded
The signature is HMAC-SHA256(request_body, your_secret).
Verify in Python:
import hashlib, hmac
def verify_webhook(body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
# In your webhook handler:
body = request.get_data() # raw bytes
sig = request.headers.get("X-PausePoint-Signature", "")
if not verify_webhook(body, sig, "your-webhook-signing-secret"):
return "Unauthorized", 401
Verify in Node.js:
const crypto = require("crypto");
function verifyWebhook(body, signatureHeader, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(body).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
// In your webhook handler (Express):
app.post("/pausepoint-webhook", express.raw({ type: "*/*" }), (req, res) => {
const sig = req.headers["x-pausepoint-signature"];
if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Unauthorized");
}
const event = JSON.parse(req.body);
// handle event...
res.sendStatus(200);
});
Delivery Behavior#
- Webhooks are delivered asynchronously via a background worker
- Timeout: 5 seconds per delivery attempt
- Retries: 3 attempts with exponential backoff (30s, 60s, 120s) on non-2xx responses
- Your endpoint must return a 2xx status within 5 seconds
- Delivery failures after 3 retries are logged but not retried further
List and Delete Webhooks#
# List
curl https://api.pausepoint.dev/v1/webhooks \
-H "Authorization: Bearer $PP_KEY"
# Delete
curl -X DELETE https://api.pausepoint.dev/v1/webhooks/{webhook_id} \
-H "Authorization: Bearer $PP_KEY"