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

# Create a webhook endpoint

> Registers an outbound webhook endpoint that receives a signed JSON payload whenever a subscribed event fires, using `enabled_events` to choose which events (the older `events` field is accepted but deprecated). The response includes a one-time plaintext signing secret used to verify each delivery's `Arcus-Signature` header, store it immediately since it is never shown again; requires `webhooks:manage` scope.



## OpenAPI

````yaml /openapi.yaml post /webhook_endpoints
openapi: 3.1.0
info:
  title: Arcus ERP Public API
  version: 1.0.0
  description: >
    Arcus ERP public REST API. Designed for external integrations and data
    migration.


    **Authentication.** Bearer token (API key) via the `Authorization` header.

    Format: `Authorization: Bearer ark_live_ent_<code>_<random>` (or
    `ark_test_*` for sandbox).

    API keys are issued per-entity in **Settings > Developers > API Keys**.


    **Entity scoping.** The entity is encoded in the API key prefix; routes are
    flat

    (e.g. `/v1/accounts`, `/v1/orders`, `/v1/products`). A small set of platform
    endpoints

    (migration, reconciliation, events, webhook endpoints, API keys) use the

    `/v1/entities/{entity_id}/...` form -- those are noted in their tags.


    **Key capabilities.**
      - Related-resource hydration via `?expand[]=` (see `x-arcus-expand` on each resource).
      - Cursor-based pagination (`starting_after` / `ending_before` / `limit`).
      - Idempotency via the `Idempotency-Key` header.
      - Webhook events for asynchronous notification.
      - Conditional requests / ETag for cache validation.

    **Changelog.** Entries are dated and name every published contract whose
    MEANING moved, not

    only the ones whose field names changed. The narrative version of the same
    entries, written

    for integrators, is published at https://arcuserp.mintlify.app/changelog.


    **2026-09-22 (planned 2026-09-21), REORDER-BUYER-TRUTH: demand changed what
    it MEANS on three

    published contracts, with no field renamed.** An integrator that pins field
    names sees no

    breakage and different numbers, which is why this entry exists.

      - **Demand now counts build consumption.** `demand_avg_per_day` on the public product, kit
        and inventory-balance objects, and `daily_demand` / `demand_basis` / `net_suggested_qty`
        on `GET /v1/purchasing/reorder-report`, are composed from fulfilled sales lines PLUS
        posted `build_consume` inventory draws: each physical decrement of a product counts
        exactly once. The previous rule adopted an internal-consumption basis only when the sales
        blend was exactly zero, so a product both sold AND consumed into work orders planned as
        if the build draws did not exist. Products drawn into work orders move; on one
        production-shaped dataset five did, the largest from 0.05/day to 14.95/day.
      - **`demand_basis` now carries four values, not two:** `sales`, `sales_and_builds`,
        `builds` and `consumption`. A consumer with a two-branch reader (anything that is not
        `consumption` is `sales`) silently hides the two new ones.
      - **`current_demand_units` on `GET /v1/inventory/balances/:id` moved, by a second rule.**
        It counts committed-but-unshipped CUSTOMER demand, and it now counts a kit component's
        own line rather than its parent kit line, and excludes non-sales documents. On one
        production dataset 269 of 1,043 balance rows changed, 227 of them downward; the largest
        single move was 1,941 to 25, on a product whose open PURCHASE order line had been
        reported as customer demand. Re-baseline anything that alerts or reorders off this field.
      - `run_rate_30/90/180/365` and `blended_daily` on the same balance object move for the
        first reason above.
      - **Added, not changed:** `GET /v1/purchasing/reorder-report` accepts `demand_window` and
        returns the unit, cover-through and reorder-point-provenance fields documented on that
        operation; `order_multiple` is accepted and returned on the product-vendor doors.
      - **Volume note.** The `inventory.low_stock`, `inventory.out_of_stock` and
        `inventory.back_in_stock` webhook events are population-gated on
        `on_hand <= reorder_point`, and reorder points move with the demand above, so
        subscribers should expect a one-time step change in event volume around the release.
servers:
  - url: https://api.arcuserp.com/v1
    description: Arcus ERP API (accepts both live `ark_live_*` and test `ark_test_*` keys)
  - url: https://dev-api.arcuserp.com/v1
    description: >-
      Dev sandbox API (test-only data, accepts `ark_test_*` keys against dev
      RDS)
security: []
paths:
  /webhook_endpoints:
    post:
      tags:
        - Webhooks
      summary: Create a webhook endpoint
      description: >-
        Registers an outbound webhook endpoint that receives a signed JSON
        payload whenever a subscribed event fires, using `enabled_events` to
        choose which events (the older `events` field is accepted but
        deprecated). The response includes a one-time plaintext signing secret
        used to verify each delivery's `Arcus-Signature` header, store it
        immediately since it is never shown again; requires `webhooks:manage`
        scope.
      operationId: createWebhookEndpointV1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
                - enabled_events
              properties:
                url:
                  type: string
                  format: uri
                  description: >-
                    HTTPS endpoint Arcus will POST events to. Must not be a
                    private/loopback address.
                enabled_events:
                  type: array
                  items:
                    type: string
                  description: >
                    Event types this endpoint subscribes to. Use `*` for all 118
                    events,

                    `<family>.*` for a whole family (e.g. `order.*`), or
                    individual event

                    names (e.g. `order.confirmed`). Must be non-empty.
                  example:
                    - order.confirmed
                    - payment.succeeded
                    - fulfillment.shipped
                events:
                  type: array
                  items:
                    type: string
                  description: >
                    **Deprecated.** Use `enabled_events` instead. Accepted for
                    back-compat;

                    ignored when `enabled_events` is also present. Removed in
                    v2.
                description:
                  type: string
                  nullable: true
                  description: Human-readable label for this endpoint (optional).
                mode:
                  type: string
                  enum:
                    - live
                    - test
                  default: live
                  description: >-
                    `live` endpoints receive production events; `test` endpoints
                    receive test-mode events only.
                api_version:
                  type: string
                  nullable: true
                  description: >-
                    Pin this endpoint to a specific API version for payload
                    serialization (optional).
                metadata:
                  type: object
                  description: >-
                    Arbitrary key-value pairs (up to 50 keys). Stored and
                    returned as-is.
                send_test_event_on_create:
                  type: boolean
                  default: false
                  description: >
                    When true, queues an immediate `webhook.test` delivery to
                    this endpoint

                    on creation. The response includes `test_event.event_id` and

                    `test_event.queued_delivery_id`. Delivery fires once
                    INFRA-011

                    (webhook deliverer Lambda) is provisioned.
      responses:
        '201':
          description: >-
            Webhook endpoint created. The `secret` field is present ONCE in this
            response only.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/WebhookEndpoint'
                  - type: object
                    properties:
                      secret:
                        type: string
                        description: >-
                          One-time plaintext signing secret (format
                          `whsec_<hex>`). Store immediately.
                        example: '[REDACTED, see CREDENTIALS.env]'
                      test_event:
                        type: object
                        nullable: true
                        description: Present when `send_test_event_on_create=true`.
                        properties:
                          event_id:
                            type: string
                            format: uuid
                          queued_delivery_id:
                            type: string
                            format: uuid
                            nullable: true
                          status:
                            type: string
                            enum:
                              - queued_pending_infra_011
                          infra_011_notice:
                            type: string
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
        '409':
          description: >-
            duplicate_url: A live/test-mode endpoint for this URL already
            exists.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: duplicate_url
                  existing_id:
                    type: string
                    format: uuid
        '422':
          $ref: '#/components/responses/Error'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    WebhookEndpoint:
      type: object
      description: >
        An outbound webhook endpoint subscription. Arcus delivers signed JSON
        payloads

        to the `url` whenever a subscribed event fires.


        **Field names:** the subscription list is `enabled_events` (Stripe
        convention).

        The deprecated alias `events` is accepted in request bodies for
        back-compat

        and will be removed in v2.
      properties:
        id:
          type: string
          format: uuid
        object:
          type: string
          enum:
            - webhook_endpoint
        entity_id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
          description: HTTPS endpoint receiving events.
        description:
          type: string
          nullable: true
        enabled_events:
          type: array
          items:
            type: string
          description: >
            Event types this endpoint subscribes to. Wildcards supported: `*`
            (all 118 events)

            or `<family>.*` (e.g. `order.*`).
          example:
            - order.confirmed
            - payment.succeeded
            - fulfillment.shipped
        secret_last4:
          type: string
          nullable: true
          description: Last 4 hex chars of the signing secret (for identification only).
        status:
          type: string
          enum:
            - active
            - paused
            - disabled
          description: >-
            `active` receives events; `paused` skips delivery (retains
            subscription); `disabled` is permanently off.
        mode:
          type: string
          enum:
            - live
            - test
          description: >-
            `live` endpoints receive production events; `test` endpoints receive
            test-mode events only.
        api_version:
          type: string
          nullable: true
        success_count:
          type: integer
          description: Total successful deliveries (HTTP 2xx).
        failure_count:
          type: integer
          description: Total failed deliveries (non-2xx or timeout).
        consecutive_failure_count:
          type: integer
          description: >-
            Consecutive failures since last success. 5+ consecutive failures
            auto-pauses the endpoint.
        last_delivery_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        metadata:
          type: object
          description: Arbitrary key-value pairs stored by the creator.
    ErrorEnvelope:
      type: object
      description: |
        Canonical error response envelope. All API errors use this shape.
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: Machine-readable error key
          example: not_found
        code:
          type: string
          description: Machine-readable error code (often same as error)
          example: not_found
        type:
          type: string
          enum:
            - validation_error
            - permission_error
            - not_found
            - conflict
            - rate_limit
            - internal
            - expand_error
            - not_implemented
          example: not_found
        hint:
          type: string
          description: Human-readable one-sentence explanation (English)
          example: >-
            The requested order does not exist or does not belong to this
            entity.
        param:
          type: string
          description: The parameter that caused the error, if applicable
          example: expand[0]
        required:
          type: string
          description: The scope required (only on insufficient_scope errors)
          example: accounts:read
        request_id:
          type: string
          description: >-
            Unique request ID for support tracing (maps to CloudWatch log
            stream)
          example: req_abc123
  responses:
    Error:
      description: Error response (400/401/403/404/409/422/429/500)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: |
        API key issued per entity via Settings > Developers > API Keys.
        Each key carries scopes (e.g. orders:read, products:write).
        Bearer token format: Authorization: Bearer ark_live_ent_<code>_<random>
        Test keys use ark_test_ent_<code>_<random>. Both are issued per entity
        via Settings > Developers > API Keys.

````

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