Layers
Partner APIAPI referenceWebhooks

POST /v1/webhook-endpoints

Register a new webhook endpoint. Returns the signing secret once — store it immediately.

View as Markdown
POST/v1/webhook-endpoints
Phase 1stableidempotent
Auth
X-Api-Key

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.

Headers
  • Idempotency-Key
    string (UUID)optional
    Replays within 24h return the original response.
Body
  • url
    stringrequired
    HTTPS URL. http://localhost is accepted in dev only.
  • events
    string[]required
    1–50 event types from the catalog. See overview for the full list.
  • description
    stringoptional
    Human label shown in the admin UI + delivery logs.
  • projectId
    string (UUID)optional
    If set, only events for this project are delivered.

Example request

curl -X POST https://api.layers.com/v1/webhook-endpoints \
  -H "X-Api-Key: $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: {
    "X-Api-Key": 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={
        "X-Api-Key": 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

201Created — signing secret returned once
{
  "endpoint": {
    "id": "d4c71b62-7f08-4dc9-9d2c-8f7e2b9c4411",
    "organizationId": "2481fa5c-a404-44ed-a561-565392499abc",
    "projectId": null,
    "url": "https://your-app.example.com/layers/webhooks",
    "description": "prod reconciler",
    "status": "active",
    "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

StatusCodeWhen
422VALIDATIONURL invalid, events empty, unknown event type, >50 events.
404NOT_FOUNDprojectId doesn't belong to the calling org.
409IDEMPOTENCY_CONFLICTIdempotency-Key reused with a different body.

See also

On this page