POST /v1/webhook-endpoints
Register a new webhook endpoint. Returns the signing secret once - store it immediately.
/v1/webhook-endpoints- Auth
- Bearer
Creates a new webhook subscription under the calling org. Pass projectId to narrow to a single project (events unrelated to that project are dropped at emit time); omit it for an org-wide endpoint.
The response carries a signingSecret shown exactly once. Store it in your secrets manager before the response closes - we cannot display it again. Use it to verify every incoming delivery via the signature helper in the webhooks overview.
Idempotency-Keystring (UUID)optionalReplays within the idempotency window return the original response.
urlstringrequiredHTTPS URL. http://localhost is accepted in dev only.eventsstring[]required1–50 event types from the catalog. See overview for the full list.descriptionstringoptionalHuman label shown in the admin UI + delivery logs.metadataobjectoptionalOptional opaque key/value pairs for your own bookkeeping (Stripe-style). Returned on reads; Layers never reads or indexes it.projectIdstring (UUID)optionalIf set, only events for this project are delivered.scope"own" | "all_children"optionalDelivery scope.own(default) delivers only this org's events.all_childrenis the firehose — this org's events PLUS every direct child's, each tagged withdata.organizationId. Settingall_childrenrequires anorg:adminkey.
Firehose (scope: "all_children"). If you run customers as sub-organizations, one parent endpoint can receive every child's events instead of registering an endpoint per child. Each delivery carries data.organizationId so you can attribute it to the right customer. Only a parent org:admin key may open one; a non-admin key requesting it gets 403 FORBIDDEN_SCOPE.
Example request
curl -X POST https://api.layers.com/v1/webhook-endpoints \
-H "Authorization: Bearer $LAYERS_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/layers/webhooks",
"events": ["job.completed", "job.failed", "content.approved"],
"description": "prod reconciler"
}'import { randomUUID } from "node:crypto";
const res = await fetch("https://api.layers.com/v1/webhook-endpoints", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.LAYERS_API_KEY}`,
"Idempotency-Key": randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://your-app.example.com/layers/webhooks",
events: ["job.completed", "job.failed", "content.approved"],
description: "prod reconciler",
}),
});
const { endpoint, signingSecret } = await res.json();
// Persist `signingSecret` to your secret store immediately.import os, uuid, requests
res = requests.post(
"https://api.layers.com/v1/webhook-endpoints",
headers={
"Authorization": f"Bearer {os.environ[\'LAYERS_API_KEY\']}",
"Idempotency-Key": str(uuid.uuid4()),
"Content-Type": "application/json",
},
json={
"url": "https://your-app.example.com/layers/webhooks",
"events": ["job.completed", "job.failed", "content.approved"],
"description": "prod reconciler",
},
)
body = res.json()
# Persist body["signingSecret"] to your secret store immediately.Response
{
"endpoint": {
"id": "d4c71b62-7f08-4dc9-9d2c-8f7e2b9c4411",
"organizationId": "org_2481fa5c-a404-44ed-a561-565392499abc",
"projectId": null,
"url": "https://your-app.example.com/layers/webhooks",
"description": "prod reconciler",
"status": "active",
"scope": "own",
"events": ["job.completed", "job.failed", "content.approved"],
"apiVersion": "v1",
"createdAt": "2026-04-20T18:28:00.000Z",
"updatedAt": "2026-04-20T18:28:00.000Z",
"lastSuccessAt": null,
"lastFailureAt": null,
"consecutiveFailureCount": 0
},
"signingSecret": "whsec_s3cret...",
"warning": "Signing secret shown once. Store it securely - rotate via POST /v1/webhook-endpoints/:id/rotate-secret if compromised."
}Errors
| Status | Code | When |
|---|---|---|
| 422 | VALIDATION | URL invalid, events empty, unknown event type, >50 events. |
| 404 | NOT_FOUND | projectId doesn't belong to the calling org. |
| 403 | FORBIDDEN_SCOPE | scope: "all_children" requested without an org:admin key. |
| 409 | IDEMPOTENCY_CONFLICT | Idempotency-Key reused with a different body. |
See also
- Webhooks overview - signing, retry, dedupe semantics
- Rotate secret
- Test endpoint