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

# Enqueue a synthetic test delivery

> Creates a `webhook_delivery` row for `:id` carrying a synthetic
event. The synthetic event is NOT persisted to `/v1/events`;
test sends shouldn't pollute the durable event tail subscribers
consume. The DeliveryWorker delivers it like any other.

`event_type` defaults to `"webhook.test"`. `payload` defaults to
`{"ping": true, "test": true}`. Both are optional.




## OpenAPI

````yaml /ledger/api-reference/openapi.yaml post /ledger/v1/webhook_endpoints/{id}/test_send
openapi: 3.1.0
info:
  title: Kordio Ledger API
  version: v1
  summary: Double-entry ledger as a service.
  description: >
    A double-entry ledger you can call over HTTP. Every write balances

    per currency, is idempotent by client key, and is append-only.

    Corrections are reversal transactions, not edits.


    This is one of two APIs on `api.kordio.io`. The other is the

    [spend control
    API](https://docs.kordio.io/agents/api-reference/introduction),

    which governs what AI agents may spend. They share a host and nothing else:

    different credentials, different envelopes, different pagination.


    New here? Start with the

    [ledger quickstart](https://docs.kordio.io/ledger/quickstart).


    ### Response envelope


    Every successful response is shaped as:


    ```json

    { "data": { "object": "<type>", ... }, "livemode": false, "request_id":
    "req_..." }

    ```


    Lists are flat (Stripe convention):


    ```json

    { "object": "list", "data": [...], "has_more": false, "next_cursor": null,
      "livemode": false, "request_id": "req_..." }
    ```


    Errors:


    ```json

    { "error": { "code": "...", "message": "...", "hint": "...", "docs_url":
    "...",
                 "request_id": "req_..." },
      "livemode": false, "request_id": "req_..." }
    ```


    `request_id` is also returned on the `X-Request-Id` header.


    ### Modes


    Each OAuth client is registered as `test` or `live`. The mode is

    encoded into the token and surfaced as the top-level `livemode`

    boolean on every response. Test and live datasets are operationally

    distinct: no `?test=true` flag, no leakage.


    ### Idempotency


    Required on writes. Same `(ledger_id, Idempotency-Key)` always

    returns the original transaction, forever. Header or body, your

    choice. Header wins if both are present.


    ### Rate limits


    Limits are enforced per OAuth client, separately for read and write

    requests. Every response carries `X-RateLimit-Limit`,

    `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (seconds until the

    window resets). When a client exceeds its limit the request is

    rejected with `429` and a `Retry-After` header giving the number of

    seconds to wait before retrying.


    ### Ledger selection


    Every ledger-scoped endpoint (everything under `/v1/` except

    `/v1/ledgers`, `/v1/organizations`, and the OAuth endpoints) needs

    to know which ledger to operate on. The ledger is resolved from,

    in order: the access token's `ledger_id` claim, the `X-Ledger-Id`

    request header, or a `ledger_id` path parameter. List your ledgers

    via `GET /v1/ledgers`.
  contact:
    name: Kordio engineering
    email: support@kordio.io
    url: https://docs.kordio.io
  license:
    name: Proprietary
    url: https://kordio.io/terms-and-conditions
servers:
  - url: https://api.kordio.io
    description: Production
  - url: http://localhost:4000
    description: Local development
security:
  - oauth2_read:
      - ledger:read
  - oauth2_write:
      - ledger:write
tags:
  - name: Authentication
    description: |
      OAuth 2.0 client_credentials. Every machine call needs a Bearer
      token; humans get one through the dashboard, services mint their
      own.
  - name: Accounts
    description: |
      Named buckets of value. Caller-chosen IDs (`cash:usd`,
      `accounts_receivable:acme`, `revenue:subscriptions`), caller-chosen
      currencies (`USD`, `USDC`, `BTC`, `POINTS`, anything goes), one of
      five accounting types.
  - name: Transactions
    description: |
      Balanced sets of postings. The unit of mutation. Reverse with
      `POST /v1/transactions/:id/reverse`; mutate `metadata` only with
      `PATCH`.
  - name: Postings
    description: |
      The atomic ledger entry. Always part of a transaction, never
      created or modified standalone.
  - name: Events
    description: |
      Same payloads webhooks deliver. The events log is the audit trail
      of every state-changing operation.
  - name: Webhooks
    description: |
      Endpoints that receive signed event payloads. Signatures are
      `HMAC-SHA256(secret, "<timestamp>.<raw_body>")` carried in the
      `Kordio-Signature` header.

      A delivery is considered successful on any 2xx response. Failures
      (non-2xx, timeout, or network error) are retried with exponential
      backoff at 30s, 5m, 1h, 6h, and 24h, for a maximum of 5 attempts;
      after the 5th failure the delivery is marked dead and not retried.
  - name: Reports
    description: |
      Read-only financial reports. Trial balance for tenant health,
      per-account statement for a period, point-in-time balance via
      `?at=<ts>` on the balance endpoint.
  - name: Reserves
    description: |
      Sweep / release / claw funds held against a counterparty.
      Reserves are accounts with `account_kind = "restricted"` and a
      `counterparty_ref`; listing and balance reads use the standard
      `/v1/accounts` surface. New event types `reserve.swept`,
      `reserve.released`, `reserve.clawed`.
  - name: Reconciliation
    description: |
      Match external system snapshots (custody balances, settlement
      confirmations, blockchain receipts) against unreconciled
      postings. Mark matched postings as reconciled with an external
      reference.
  - name: Sources
    description: |
      Named external systems and their ingested external transactions.
      Push statement rows once, idempotently, then run reconciliation
      per source. `GET /v1/external_transactions?status=open` is the
      break list.
  - name: Periods
    description: |
      Close and reopen accounting periods. A closed period rejects new
      postings whose `value_date` falls inside it, enforced both at
      the application layer and at the database via trigger.
  - name: Ledgers
    description: |
      A ledger is a single namespace of accounts, transactions, and
      postings, bound to one mode (test/live). Most resources are
      scoped to a ledger.
  - name: Organizations
    description: |
      Top-level tenant container. Owns ledgers, members, invitations.
      Org-scoped endpoints don't require `X-Ledger-Id`.
  - name: System
    description: Health, metadata, discovery.
paths:
  /ledger/v1/webhook_endpoints/{id}/test_send:
    post:
      tags:
        - Webhooks
      summary: Enqueue a synthetic test delivery
      description: |
        Creates a `webhook_delivery` row for `:id` carrying a synthetic
        event. The synthetic event is NOT persisted to `/v1/events`;
        test sends shouldn't pollute the durable event tail subscribers
        consume. The DeliveryWorker delivers it like any other.

        `event_type` defaults to `"webhook.test"`. `payload` defaults to
        `{"ping": true, "test": true}`. Both are optional.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                event_type:
                  type: string
                  example: webhook.test
                payload:
                  type: object
      responses:
        '201':
          description: Delivery enqueued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDelivery'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Endpoint is disabled (`webhook_endpoint_disabled`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - oauth2_write:
            - ledger:write
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        minLength: 1
        maxLength: 255
        pattern: ^[A-Za-z0-9_:.\-]+$
        example: pi_acme_1234_capture
      description: |
        Required on writes. Stable identifier you choose. The same key
        always returns the same transaction, forever. Can also be supplied as
        `idempotency_key` in the request body. Header wins.

        Allowed character set: `A-Z`, `a-z`, `0-9`, `_`, `:`, `.`, `-`.
        Max 255 bytes. Replays of an accepted key return the original
        response with header `Idempotent-Replayed: true` so callers can
        tell a replay from a freshly-committed result.
  schemas:
    WebhookDelivery:
      allOf:
        - $ref: '#/components/schemas/EnvelopeFields'
        - type: object
          required:
            - object
            - id
            - webhook_endpoint_id
            - event_id
            - status
            - attempt
          properties:
            object:
              type: string
              enum:
                - webhook_delivery
            id:
              type: string
              format: uuid
            webhook_endpoint_id:
              type: string
              format: uuid
            event_id:
              type: string
            enqueue_kind:
              type: string
              enum:
                - event
                - redelivery
                - test_send
              description: How this delivery was created.
            attempt:
              type: integer
              description: How many HTTP attempts this row has made (0 for pending).
            status:
              type: string
              enum:
                - pending
                - succeeded
                - failed
              description: |
                Public delivery state. Internally the DB also carries a
                `dead` value for terminal failures after all retries
                exhausted; the API collapses that to `failed`.
            request_method:
              type: string
              nullable: true
              example: POST
            request_url:
              type: string
              nullable: true
            request_headers:
              type: object
              additionalProperties:
                type: string
              nullable: true
              description: |
                Outgoing request headers as sent to the integrator's
                endpoint. Sensitive headers (`authorization`, `*secret*`,
                `cookie`) are redacted to `[redacted]`. The
                `Kordio-Signature` header is preserved because it's an
                HMAC value, not a key.
            request_body:
              type: string
              nullable: true
            response_status:
              type: integer
              nullable: true
            response_headers:
              type: object
              additionalProperties:
                type: string
              nullable: true
            response_body:
              type: string
              nullable: true
            response_at:
              type: string
              format: date-time
              nullable: true
            next_attempt_at:
              type: string
              format: date-time
              nullable: true
            error_message:
              type: string
              nullable: true
            redelivers_delivery_id:
              type: string
              format: uuid
              nullable: true
              description: 'For `enqueue_kind: redelivery`, the source delivery id.'
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
    ErrorResponse:
      type: object
      required:
        - error
        - livemode
        - request_id
      properties:
        livemode:
          type: boolean
          example: false
        request_id:
          type: string
          example: req_3LhM8XKx9q4hQv
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable. Stable across versions.
              enum:
                - invalid_request
                - missing_idempotency_key
                - unauthorized
                - forbidden
                - insufficient_scope
                - invalid_client
                - invalid_grant
                - invalid_scope
                - not_found
                - unbalanced
                - currency_mismatch
                - unknown_account
                - already_exists
                - already_reversed
                - immutable_field
                - insufficient_funds
                - period_closed
                - precondition_failed
                - duplicate_external_ref
                - partial_refund_exceeds_original
                - refund_currency_required
                - rotation_in_progress
                - webhook_endpoint_disabled
                - reserve_insufficient_funds
                - reserve_currency_mismatch
                - reserve_release_exceeds_balance
                - account_template_unknown
                - invalid_state
                - unsupported_grant_type
                - rate_limited
                - internal_error
                - tenant_suspended
                - fresh_authentication_required
            message:
              type: string
              description: Human-readable one-liner.
            hint:
              type: string
              description: Actionable next step.
            param:
              type: string
              nullable: true
              description: Which request field caused the failure.
            details:
              type: object
              additionalProperties: true
              description: >-
                Code-specific structured detail (e.g. `by_currency` for
                unbalanced).
            docs_url:
              type: string
              format: uri
            request_id:
              type: string
              description: Echoes the top-level `request_id`. Stripe-compatible placement.
    EnvelopeFields:
      type: object
      properties:
        object:
          type: string
          description: >-
            Discriminator naming the shape of this resource (e.g. `account`,
            `transaction`).
          example: account
  responses:
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            default:
              value:
                error:
                  code: not_found
                  message: account not found
                  param: id
                  docs_url: https://docs.kordio.io/errors#not_found
  securitySchemes:
    oauth2_read:
      type: oauth2
      description: |
        OAuth 2.0 client_credentials grant. Send `client_id:client_secret`
        as HTTP Basic, or as form fields. Tokens are HS256 JWTs valid for
        one hour (configurable). Decode them at jwt.io.
      flows:
        clientCredentials:
          tokenUrl: /oauth/token
          scopes:
            ledger:read: Read accounts, balances, transactions, postings, events
            ledger:clients:read: List and inspect OAuth clients (no secrets)
    oauth2_write:
      type: oauth2
      description: Same flow; `ledger:write` is required for any mutation.
      flows:
        clientCredentials:
          tokenUrl: /oauth/token
          scopes:
            ledger:write: >-
              Create / patch / reverse / commit transactions and accounts;
              manage webhook endpoints
            ledger:clients:write: Create, rotate and delete OAuth clients

````