> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alura.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Create webhook

> Create a webhook endpoint: an HTTPS URL that receives your connected shops' Etsy events, filtered by event type and shop.

**Cost:** Free.

```bash
curl -X POST "https://api.alura.io/v1/webhooks" \
  -H "Authorization: Bearer $ALURA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9b2f4c1e-7a3d-4e8b-b6c5-1d0e2f3a4b5c" \
  -d '{"url": "https://example.com/alura/webhooks", "event_types": ["order.*"], "description": "Order notifications"}'
```

Needs the `webhooks:write` scope. Events start reaching the endpoint about 30 seconds after you create it.

> [!NOTE]
>
> The answer carries the endpoint's signing secret (`whsec_...`) once. Store it: you need it to [verify deliveries](https://docs.alura.io/guides/verifying-webhooks.md), and no other call shows it again.

## Choose events and shops

| `event_types` entry | Receives |
| - | - |
| `*` | Every event type |
| `order.paid` | That exact type |
| `order.*` | Every type that starts with `order.` |

Give up to 20 entries. Alura doesn't keep a list of Etsy's types, so a type Etsy adds later reaches every entry that matches it.

Order events reveal that an order exists, so `*`, `order.*` and any `order.` type need the `orders:read` scope as well. Without it, the call answers `403` [`insufficient_scope`](https://docs.alura.io/errors/insufficient_scope).

`shop_ids` limits the endpoint to up to 100 of your connected shops. Leave it out, or set it to `null`, for every connected shop, including shops connected later. A shop that isn't connected to the workspace answers `404`.

## The URL

It must be HTTPS on a public host: not `localhost` or a private, loopback, link-local or metadata address. No credentials or fragment in it, and at most 2,048 characters once percent-encoded.

> [accordion] Events before your first endpoint
>
> Alura stores a workspace's events only from its first endpoint on. Nothing from before exists, in deliveries or in [List events](https://docs.alura.io/api-reference/events/list.md).

> [accordion] Limits and retries
>
> A workspace has at most 10 endpoints. Creating an 11th answers `409` [`conflict`](https://docs.alura.io/errors/conflict).
>
> Send an `Idempotency-Key` to retry safely: a retry with the same key and request answers the first result, with the same secret. See [Idempotency](https://docs.alura.io/api-reference/introduction.md#idempotency).

Next, [send a test event](https://docs.alura.io/api-reference/webhooks/endpoints/test.md) and check it arrives. How deliveries and retries work is on [Webhooks and events](https://docs.alura.io/guides/webhooks-and-events.md).


## OpenAPI

````yaml /openapi.json post /v1/webhooks
openapi: 3.1.0
info:
  title: Alura API
  version: 1.0.0
  description: Etsy market research for scripts and AI assistants.
servers:
  - url: https://api.alura.io
security:
  - apiKey: []
paths:
  /v1/webhooks:
    post:
      summary: Create webhook
      description: >-
        An HTTPS URL that receives the workspace's Etsy events from about 30
        seconds after it is created, filtered by event type and shop. The answer
        carries the endpoint's signing secret, once. Events exist only from the
        workspace's first endpoint on. Order events need `orders:read` as well.
        At most 10 endpoints per workspace. Retry safely with `Idempotency-Key`.
        Free.


        Cost: free.
      operationId: webhooks.endpoints.create
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Makes a retry safe: the same key with the same request within 24
            hours returns the first result (with `Idempotent-Replayed: true`)
            instead of writing again. The same key with a different request is a
            409 `conflict`; while the first request still runs, 409
            `idempotency_processing`.
          schema:
            type: string
            minLength: 1
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  description: 'The HTTPS URL Alura sends events to: a public host.'
                  type: string
                  maxLength: 2048
                event_types:
                  description: >-
                    Event types to receive: `*` for every type, an exact type
                    such as `order.paid`, or a prefix such as `order.*`. Order
                    events need the `orders:read` scope too.
                  minItems: 1
                  maxItems: 20
                  type: array
                  items:
                    type: string
                    maxLength: 200
                    pattern: ^(\*|[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*(\.\*)?)$
                shop_ids:
                  anyOf:
                    - minItems: 1
                      maxItems: 100
                      type: array
                      items:
                        type: string
                        pattern: ^[1-9]\d{0,18}$
                    - type: 'null'
                  description: >-
                    Only events of these connected shops (Etsy shop IDs). Leave
                    out or null for every connected shop, including shops
                    connected later. A shop that isn't connected to the
                    workspace is 404.
                description:
                  anyOf:
                    - type: string
                      maxLength: 500
                    - type: 'null'
                  description: A note for yourself, up to 500 characters.
              required:
                - url
                - event_types
              additionalProperties: false
            example:
              url: https://example.com/alura/webhooks
              event_types:
                - order.*
      responses:
        '200':
          description: OK
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            Alura-Credits-Cost:
              $ref: '#/components/headers/Alura-Credits-Cost'
            Alura-Credits-Remaining:
              $ref: '#/components/headers/Alura-Credits-Remaining'
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                    const: webhook_endpoint
                  id:
                    type: string
                    description: '`we_...`'
                  url:
                    type: string
                    description: 'Where events are sent: an HTTPS URL.'
                  description:
                    description: Your note for this endpoint; null when none was set.
                    type:
                      - string
                      - 'null'
                  event_types:
                    type: array
                    items:
                      type: string
                    description: >-
                      Etsy's event types this endpoint receives: `*` for every
                      type, an exact type such as `order.paid`, or a prefix such
                      as `order.*`.
                  shop_ids:
                    anyOf:
                      - type: array
                        items:
                          type: string
                      - type: 'null'
                    description: >-
                      Only events of these connected shops; null for every
                      connected shop of the workspace, including shops connected
                      later.
                  status:
                    type: string
                    enum:
                      - enabled
                      - disabled
                    description: >-
                      `enabled` receives events; `disabled` receives none until
                      enabled again.
                  disabled_reason:
                    anyOf:
                      - type: string
                        enum:
                          - manual
                          - failing
                      - type: 'null'
                    description: >-
                      Why a disabled endpoint is disabled: `manual` (PATCH) or
                      `failing` (deliveries kept failing). Null while enabled.
                  created_at:
                    type: string
                    description: When the endpoint was created (ISO 8601, UTC).
                  secret:
                    type: string
                    description: >-
                      The signing secret (`whsec_...`). Shown once, in this
                      answer; store it to verify deliveries.
                required:
                  - object
                  - id
                  - url
                  - description
                  - event_types
                  - shop_ids
                  - status
                  - disabled_reason
                  - created_at
                  - secret
                additionalProperties: false
              example:
                object: webhook_endpoint
                id: we_01JMOCK0000000000000000000
                url: https://example.com/alura/webhooks
                description: null
                event_types:
                  - order.*
                shop_ids: null
                status: enabled
                disabled_reason: null
                created_at: '2026-10-22T09:00:00.000Z'
                secret: whsec_ExampleExampleExampleExampleExampleExam0=
        '400':
          $ref: '#/components/responses/Problem400'
        '401':
          $ref: '#/components/responses/Problem401'
        '402':
          $ref: '#/components/responses/Problem402'
        '403':
          $ref: '#/components/responses/Problem403'
        '404':
          $ref: '#/components/responses/Problem404'
        '405':
          $ref: '#/components/responses/Problem405'
        '409':
          $ref: '#/components/responses/Problem409'
        '429':
          $ref: '#/components/responses/Problem429'
        '500':
          $ref: '#/components/responses/Problem500'
        '503':
          $ref: '#/components/responses/Problem503'
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl -X POST "https://api.alura.io/v1/webhooks" \
              -H "Authorization: Bearer $ALURA_API_KEY" \
              -H "Content-Type: application/json" \
              -H "Idempotency-Key: 5f0c8e2a-4b1d-4c3e-9a7f-2b5d6e8f0a1c" \
              -d '{"url":"https://example.com/alura/webhooks","event_types":["order.*"]}'
        - lang: typescript
          label: TypeScript
          source: |-
            const res = await fetch('https://api.alura.io/v1/webhooks', {
              method: 'POST',
              headers: {
                Authorization: `Bearer ${process.env.ALURA_API_KEY}`,
                'Content-Type': 'application/json',
                'Idempotency-Key': '5f0c8e2a-4b1d-4c3e-9a7f-2b5d6e8f0a1c',
              },
              body: JSON.stringify({
                "url": "https://example.com/alura/webhooks",
                "event_types": [
                  "order.*"
                ]
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import json
            import os
            import urllib.request

            request = urllib.request.Request(
                "https://api.alura.io/v1/webhooks",
                method="POST",
                data=json.dumps({
                  "url": "https://example.com/alura/webhooks",
                  "event_types": [
                    "order.*"
                  ]
                }).encode(),
                headers={
                    "Authorization": f"Bearer {os.environ['ALURA_API_KEY']}",
                    "Content-Type": "application/json",
                    "Idempotency-Key": "5f0c8e2a-4b1d-4c3e-9a7f-2b5d6e8f0a1c",
                },
            )
            with urllib.request.urlopen(request) as response:
                print(json.load(response))
components:
  headers:
    Request-Id:
      description: '`req_` + ULID; quote it when reporting a problem.'
      schema:
        type: string
    Alura-Credits-Cost:
      description: API credits this call cost.
      schema:
        type: integer
    Alura-Credits-Remaining:
      description: >-
        API credits left in the workspace's monthly allowance, counting calls
        still in flight. Absent when it cannot be read right now.
      schema:
        type: integer
  responses:
    Problem400:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/invalid_request
            title: Invalid request
            status: 400
            code: invalid_request
            detail: Invalid request
            suggestion: Fix the request as the detail says, then send it again.
            retryable: false
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem401:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/unauthorized
            title: Unauthorized
            status: 401
            code: unauthorized
            detail: Unauthorized
            suggestion: 'Send a valid API key as `Authorization: Bearer alura_sk_...`.'
            retryable: false
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem402:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/credits_exhausted
            title: API credits exhausted
            status: 402
            code: credits_exhausted
            detail: API credits exhausted
            suggestion: Upgrade the plan or wait for the monthly reset.
            retryable: false
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem403:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/plan_required
            title: Plan required
            status: 403
            code: plan_required
            detail: Plan required
            suggestion: Upgrade the workspace plan to use this operation.
            retryable: false
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem404:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/not_found
            title: Not found
            status: 404
            code: not_found
            detail: Not found
            suggestion: Check the ID, or search for it first.
            retryable: false
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem405:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/method_not_allowed
            title: Method not allowed
            status: 405
            code: method_not_allowed
            detail: Method not allowed
            suggestion: Use one of the methods in the Allow header.
            retryable: false
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem409:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/conflict
            title: Conflict
            status: 409
            code: conflict
            detail: Conflict
            suggestion: Reload the resource and retry with its current state.
            retryable: false
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem429:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/rate_limited
            title: Rate limited
            status: 429
            code: rate_limited
            detail: Rate limited
            suggestion: Wait for Retry-After, then retry.
            retryable: true
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem500:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/internal_error
            title: Internal error
            status: 500
            code: internal_error
            detail: Internal error
            suggestion: Retry; if it keeps failing, report the request_id.
            retryable: true
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
    Problem503:
      description: Error
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          example:
            type: https://docs.alura.io/errors/credits_unavailable
            title: API credits unavailable
            status: 503
            code: credits_unavailable
            detail: API credits unavailable
            suggestion: Retry shortly.
            retryable: true
            request_id: req_01JQ5W3H8M2N4P6R8T0V2X4Z6B
  schemas:
    Problem:
      type: object
      description: RFC 9457 problem details.
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        code:
          type: string
          description: A code from the error catalog; new codes may be added.
          x-extensible-enum:
            - invalid_request
            - unknown_parameter
            - invalid_cursor
            - cursor_window_exceeded
            - invalid_reference
            - id_type_mismatch
            - invalid_keyword
            - shop_id_required
            - listing_not_publishable
            - sales_window_not_covered
            - unauthorized
            - credits_exhausted
            - plan_required
            - insufficient_scope
            - workspace_required
            - workspace_locked
            - your_shop_unavailable
            - writes_suspended
            - browser_refused
            - role_required
            - impersonation_refused
            - recent_sign_in_required
            - etsy_scope_required
            - etsy_partner_required
            - not_found
            - not_collected
            - not_found_on_etsy
            - shop_opted_out
            - method_not_allowed
            - conflict
            - key_limit_reached
            - reconnect_required
            - settings_not_fetched
            - listing_busy
            - settings_busy
            - shop_create_busy
            - idempotency_partial_unverifiable
            - listing_has_variations
            - changed_since
            - change_not_revertable
            - listing_in_ab_test
            - activation_skipped
            - idempotency_processing
            - confirmation_required
            - test_shop_active_cap
            - write_outcome_unknown
            - unprocessable
            - listing_removed_on_etsy
            - endpoint_removed
            - rate_limited
            - throttled
            - daily_credit_cap_exceeded
            - credits_reserved
            - fetch_limit_reached
            - write_rate_limited
            - sync_cooldown
            - shop_write_budget_exhausted
            - internal_error
            - write_misconfigured
            - upstream_unavailable
            - credits_unavailable
            - service_unavailable
            - fetch_unavailable
            - create_lock_unavailable
            - writes_paused
            - etsy_capacity
            - upstream_timeout
        detail:
          type: string
        suggestion:
          type: string
        retryable:
          type: boolean
        request_id:
          type: string
        upgrade_url:
          type: string
          format: uri
          description: 'credits_exhausted and plan_required: where to upgrade the plan.'
        credits_needed:
          type: integer
          description: 'API credit refusals: what the call needed.'
        credits_remaining:
          type: integer
          description: >-
            API credit refusals: what is left (of the month, or of the day for
            daily_credit_cap_exceeded).
        resets_at:
          type: string
          format: date-time
          description: >-
            credits_exhausted and daily_credit_cap_exceeded: when the limit
            resets.
      required:
        - type
        - title
        - status
        - code
        - detail
        - suggestion
        - retryable
        - request_id
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: 'An API key: `Authorization: Bearer alura_sk_...`.'

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.