Skip to content

Webhooks API

The Webhooks API has two halves. Outbound subscriptions push Puck events (investigation results, findings, agent lifecycle) to URLs you control. Inbound endpoints let external systems (CrowdStrike alerts, GitHub Actions, generic POST) trigger investigations without going through the console.

All webhook endpoints require the webhooks:manage scope unless noted otherwise.

Related concept page: Webhooks.


POST /v1/webhooks/subscriptions

Create an outbound webhook subscription. Puck will POST a signed JSON payload to url for every event type in events that passes the optional filters.

The secret field is returned exactly once. Copy it immediately — read endpoints never return it. Use the secret to verify the X-Puck-Signature-256 HMAC-SHA256 header on incoming deliveries.

Terminal window
curl https://api.puck.security/v1/webhooks/subscriptions \
-H "Authorization: Bearer $PUCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.example/puck/events",
"events": ["investigation.completed", "finding.discovered"],
"filters": { "min_severity": "high" }
}'
const res = await fetch("https://api.puck.security/v1/webhooks/subscriptions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PUCK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://your-server.example/puck/events",
events: ["investigation.completed", "finding.discovered"],
filters: { min_severity: "high" },
}),
});
const { id, secret } = await res.json();
// Store secret securely — never returned again

No request parameters or body.

Status: 201 — Subscription created.

Empty response body.

StatusMeaning
400Validation error.
403API key lacks `webhooks:manage`.

GET /v1/webhooks/subscriptions

List all outbound webhook subscriptions for the account. Secrets are never included in list or get responses.

Terminal window
curl https://api.puck.security/v1/webhooks/subscriptions \
-H "Authorization: Bearer $PUCK_API_KEY"

No request parameters or body.

Status: 200 — Subscription list.

FieldTypeRequiredDescription
subscriptionsarrayyes

No documented error responses for this endpoint.


GET /v1/webhooks/subscriptions/{id}

Fetch a single subscription by ID. Returns the same shape as the list response — no secret.

Terminal window
curl "https://api.puck.security/v1/webhooks/subscriptions/sub_01hx…" \
-H "Authorization: Bearer $PUCK_API_KEY"

No request parameters or body.

Status: 200 — Subscription detail.

Empty response body.

StatusMeaning
404Not found.

PATCH /v1/webhooks/subscriptions/{id}

Update an existing subscription. All fields are optional; only the fields you include are changed. To add a new event type without removing existing ones, fetch the current subscription, append to events, and send the full array.

Terminal window
# Pause a subscription
curl -X PATCH "https://api.puck.security/v1/webhooks/subscriptions/sub_01hx…" \
-H "Authorization: Bearer $PUCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "active": false }'

No request parameters or body.

Status: 200 — Subscription updated.

Empty response body.

StatusMeaning
404Not found.

DELETE /v1/webhooks/subscriptions/{id}

Permanently delete a subscription. In-flight deliveries for this subscription are abandoned. Returns 204 No Content on success.

Terminal window
curl -X DELETE "https://api.puck.security/v1/webhooks/subscriptions/sub_01hx…" \
-H "Authorization: Bearer $PUCK_API_KEY"

No request parameters or body.

Status: 204 — Deleted.

Empty response body.

StatusMeaning
404Not found.

POST /v1/webhooks/subscriptions/{id}/test

Fire a synthetic delivery to verify your endpoint’s wiring. The test payload uses the same shape as a real delivery but with placeholder data. Returns 202 Accepted with a delivery_id you can track via GET /v1/webhooks/deliveries.

Terminal window
curl -X POST "https://api.puck.security/v1/webhooks/subscriptions/sub_01hx…/test" \
-H "Authorization: Bearer $PUCK_API_KEY"
const res = await fetch(
`https://api.puck.security/v1/webhooks/subscriptions/${id}/test`,
{
method: "POST",
headers: { Authorization: `Bearer ${process.env.PUCK_API_KEY}` },
}
);
const { delivery_id } = await res.json();

No request parameters or body.

Status: 202 — Test delivery queued.

FieldTypeRequiredDescription
delivery_idstringyes
StatusMeaning
404Not found.

GET /v1/webhooks/deliveries

List delivery attempts for outbound subscriptions. Filter by ?status=failed_permanent to see the dead-letter queue. Deliveries are retained for 30 days.

Terminal window
# Dead-letter queue for a specific subscription
curl "https://api.puck.security/v1/webhooks/deliveries?status=failed_permanent&subscription_id=sub_01hx…" \
-H "Authorization: Bearer $PUCK_API_KEY"
FieldInTypeRequiredDescription
statusqueryobjectno
subscription_idquerystringno
limitqueryintegerno

Status: 200 — Deliveries.

FieldTypeRequiredDescription
deliveriesarrayyes

No documented error responses for this endpoint.


POST /v1/webhooks/deliveries/{id}/replay

Reset a delivery to pending so the worker retries the POST to your endpoint. The replay reuses the original event_id — your endpoint must deduplicate on event_id to avoid processing the same event twice.

Terminal window
curl -X POST "https://api.puck.security/v1/webhooks/deliveries/dlv_01hx…/replay" \
-H "Authorization: Bearer $PUCK_API_KEY"

No request parameters or body.

Status: 202 — Replay queued.

FieldTypeRequiredDescription
delivery_idstringyes
statusstringyes
StatusMeaning
404Delivery not found.

POST /v1/webhooks/incoming/endpoints

Create an inbound webhook endpoint. Puck generates a per-endpoint URL secret and returns the full receiver URL exactly once. Supported providers: generic, crowdstrike, github.

Optionally supply a provider_signing_secret (e.g. the GitHub webhook secret). When set, Puck verifies the provider’s signature on each inbound POST.

Terminal window
curl https://api.puck.security/v1/webhooks/incoming/endpoints \
-H "Authorization: Bearer $PUCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "github",
"provider_signing_secret": "gh_secret_abc123",
"default_depth": "standard",
"active": true
}'
const res = await fetch("https://api.puck.security/v1/webhooks/incoming/endpoints", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PUCK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
provider: "github",
provider_signing_secret: process.env.GITHUB_WEBHOOK_SECRET,
default_depth: "standard",
active: true,
}),
});
const { id, url } = await res.json();
// url is the full receiver URL — store it securely, configure it in GitHub
FieldInTypeRequiredDescription
providerbodystringyes
provider_signing_secretbodystringnoProvider-side signing secret (e.g. GitHub webhook secret). Optional. Adds integrity verification on top of URL auth.
default_scopebodyobjectno
default_depthbodyobjectno
activebodybooleanno

Status: 201 — Endpoint created.

FieldTypeRequiredDescription
idstringyes
providerstringyes
urlstringyesFull receiver path with the URL secret. Returned exactly once.
url_key_prefixstringyes
activebooleanyes
default_scopeobjectno
default_depthstringno
created_atstringyes

No documented error responses for this endpoint.


GET /v1/webhooks/incoming/endpoints

List all inbound webhook endpoints for the account. URL secrets are never included — only the url_key_prefix for identification.

Terminal window
curl https://api.puck.security/v1/webhooks/incoming/endpoints \
-H "Authorization: Bearer $PUCK_API_KEY"

No request parameters or body.

Status: 200 — List of endpoints (without URL secrets).

FieldTypeRequiredDescription
endpointsarrayyes

No documented error responses for this endpoint.


DELETE /v1/webhooks/incoming/endpoints/{id}

Permanently delete an inbound endpoint. Any further POSTs to the endpoint’s receiver URL return 401 Unauthorized. Returns 204 No Content on success.

Terminal window
curl -X DELETE "https://api.puck.security/v1/webhooks/incoming/endpoints/ep_01hx…" \
-H "Authorization: Bearer $PUCK_API_KEY"

No request parameters or body.

Status: 204 — Deleted.

Empty response body.

StatusMeaning
404Not found.

POST /v1/webhooks/incoming/{provider}/{key}

Receive an inbound event from an external system. This is the receiver URL that Puck generates when you create an inbound endpoint. You configure this URL in your third-party tool (e.g. as a GitHub webhook payload URL); you do not call it directly from application code.

The {provider} path segment must match the provider set when the endpoint was created. The {key} segment is the URL secret. Mismatches return 401.

On success: returns 202 Accepted with { "investigation_id": "..." } for known event types, or 202 with { "ignored": true, "reason": "..." } for unrecognised event types. A failed signature check (when provider_signing_secret was set) returns 401.

Terminal window
# Example: posting a generic event
curl -X POST "https://api.puck.security/v1/webhooks/incoming/generic/abc123secret…" \
-H "Content-Type: application/json" \
-d '{
"query": "check the host that just alerted",
"agent_ids": ["agent_01hx…"]
}'

No request parameters or body.

Status: 202 — Trigger queued (or event ignored).

Empty response body.

StatusMeaning
401Invalid URL key, provider mismatch, or signature mismatch.
404Unknown provider.