Documentation menu

Docs/Webhooks

Webhooks

Register webhook endpoints to receive real-time notifications when a pause changes state. This is the recommended alternative to polling.


Register a Webhook#

bash
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:

json
{
  "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:

json
{
  "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:

text
X-PausePoint-Signature: sha256=<hex>
X-PausePoint-Event: pause.responded

The signature is HMAC-SHA256(request_body, your_secret).

Verify in Python:

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:

javascript
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#

bash
# 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"