# POST /v1/webhook-endpoints (/docs/api/reference/webhooks/create-endpoint)



<Endpoint method="POST" path="/v1/webhook-endpoints" auth="X-Api-Key" phase="1" />

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](/docs/api/operational/webhooks#verifying-the-signature).

<Parameters
  title="Headers"
  rows="[
  { name: 'Idempotency-Key', type: 'string (UUID)', description: 'Replays within 24h return the original response.' },
]"
/>

<Parameters
  title="Body"
  rows="[
  { name: 'url', type: 'string', required: true, description: 'HTTPS URL. http://localhost is accepted in dev only.' },
  { name: 'events', type: 'string[]', required: true, description: '1–50 event types from the catalog. See overview for the full list.' },
  { name: 'description', type: 'string', description: 'Human label shown in the admin UI + delivery logs.' },
  { name: 'projectId', type: 'string (UUID)', description: 'If set, only events for this project are delivered.' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    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"
      }'
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    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.
    ```
  </Tab>

  <Tab value="Python">
    ```python
    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.
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="201" description="Created — signing secret returned once">
  ```json
  {
    "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."
  }
  ```
</Response>

## Errors [#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.             |
| 409    | `IDEMPOTENCY_CONFLICT` | Idempotency-Key reused with a different body.              |

## See also [#see-also]

* [Webhooks overview](/docs/api/operational/webhooks) — signing, retry, dedupe semantics
* [Rotate secret](/docs/api/reference/webhooks/rotate-secret)
* [Test endpoint](/docs/api/reference/webhooks/test-endpoint)
