PandaStack

Outbound webhooks

Get pushed events for deployments, database failovers, and quota changes instead of polling — HMAC-signed, with automatic retries.

PandaStack can push events to your HTTPS endpoints as they happen, so integrations (CI, chat alerts, internal dashboards) don't need to poll.

Register an endpoint

curl -X POST https://api.pandastack.ai/v1/webhooks/endpoints \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks/pandastack", "events": ["deployment.succeeded", "deployment.failed"]}'

The response includes a whsec_... signing secret — store it; you need it to verify deliveries. An empty events array subscribes to everything. Up to 10 endpoints per workspace. Internal/private-network URLs are rejected.

Event catalog

EventFires when
deployment.buildingAn app deploy starts building
deployment.succeededA deploy went live (blue-green flip done)
deployment.failedA deploy failed (payload includes error)
database.failover.startedA managed-DB failover began
database.failover.completedThe database is ready on its new host
quota.warningA workspace crossed 80% of its included credit
quota.exhaustedCredit exhausted; resources are being paused
endpoint.testYou called the test route

The live list is always at GET /v1/webhooks/events. Sandbox lifecycle events (create/terminate) are on the roadmap — they live on the agent event bus and need a control-plane bridge.

Delivery format

{
  "id": "wd_1a2b3c...",
  "type": "deployment.succeeded",
  "created_at": "2026-08-14T10:00:00Z",
  "workspace": "acme",
  "data": { "deployment_id": "…", "app_id": "…", "status": "succeeded", "git_commit": "…" }
}

Headers: X-PandaStack-Event (type), X-PandaStack-Delivery (id), and X-PandaStack-Signature.

Verify signatures

X-PandaStack-Signature: t=<unix>,v1=<hex> where v1 = HMAC-SHA256(secret, "<t>." + raw_body). Always verify against the raw request bytes, and reject stale timestamps (>5 min) to prevent replays:

import hmac, hashlib, time

def verify(secret: str, sig_header: str, body: bytes, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in sig_header.split(","))
    if abs(time.time() - int(parts["t"])) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Retries

A delivery counts as successful only on a 2xx response (redirects are failures — we never follow them). Failures retry with backoff at 1m, 5m, 30m, 2h, 8h (6 attempts total), then the delivery is marked dead. Inspect recent deliveries with GET /v1/webhooks/endpoints/{id}/deliveries, and send a test event with POST /v1/webhooks/endpoints/{id}/test.

Respond 2xx quickly (before doing slow work) — the delivery timeout is 10s.

On this page