Halliday Webhooks API

Register HTTPS endpoints to receive signed notifications when a workflow reaches a terminal state, instead of polling for status. You subscribe to one or more event types per webhook. | Event type | Fires when a workflow's status becomes | | --- | --- | | `WORKFLOW_COMPLETED` | `COMPLETE` | | `WORKFLOW_FAILED` | `FAILED` | All management endpoints live under `/orgs/webhooks` and authenticate with a secret API key (passed as a bearer token) that has webhook access. Publishable keys cannot manage webhooks. **Integration checklist** - Receiver is a public HTTPS endpoint (no private IPs). - Save the `signing_secret` when you create the webhook — it is shown only once. - Verify `X-Halliday-Signature` against the raw body, accepting any of its comma-separated signatures. - Respond `2xx` quickly and do the real work afterward. - Skip deliveries whose `id` you have already handled.

Operations 5

POST /orgs/webhooks Register a webhook #
GET /orgs/webhooks List webhooks #
PATCH /orgs/webhooks Update a webhook #
DELETE /orgs/webhooks Delete a webhook #
POST /orgs/webhooks/rotate-secret Rotate the signing secret #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/halliday-webhooks-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

halliday-webhooks-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Halliday API V2 Webhooks API
  description: 'API V2 for Halliday''s payment infrastructure, supporting onramps, swaps, and offramps.


    This API provides a unified interface for cryptocurrency payments, allowing developers to:

    - Quote payments across multiple providers

    - Execute payments with onramps, swaps, and offramps

    - Track payment status and history


    ## Authentication


    API key authentication is required for all endpoints.'
  version: 2.0.0
  contact:
    name: Contact Halliday
    url: https://halliday.xyz
    email: support@halliday.xyz
servers:
- url: https://v2.prod.halliday.xyz
  description: Base domain
security:
- ApiKeyAuth: []
tags:
- name: Webhooks
  description: 'Register HTTPS endpoints to receive signed notifications when a workflow reaches a terminal

    state, instead of polling for status.'
paths:
  /orgs/webhooks:
    post:
      summary: Register a webhook
      description: 'Register an HTTPS endpoint to receive signed event notifications when a workflow reaches a

        terminal state. Halliday delivers each event as an HTTP `POST` to your registered `url`, so the

        endpoint must accept `POST` requests. On success the response includes a `signing_secret` that

        is **only ever returned on creation and rotation** — store it immediately, because it cannot be

        retrieved again from the list endpoint.


        Authenticate with a secret API key that has webhook access. Publishable keys cannot manage webhooks.'
      operationId: registerWebhook
      tags:
      - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterWebhookRequest'
            example:
              url: https://api.yourapp.com/halliday/webhooks
              label: prod-workflows
              event_types:
              - WORKFLOW_COMPLETED
              - WORKFLOW_FAILED
              auth_header:
                name: X-Webhook-Token
                value: a-shared-secret-token
      responses:
        '200':
          description: Webhook registered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
              example:
                id: 4f2c…
                label: prod-workflows
                url: https://api.yourapp.com/halliday/webhooks
                signing_secret: a1b2c3…
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
              example:
                errors:
                - code: invalid_type
                  expected: string
                  path:
                  - label
                  message: 'Invalid input: expected string, received undefined'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: Invalid API key
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: Publishable keys cannot manage webhooks
    get:
      summary: List webhooks
      description: List the webhooks registered for your org. The signing secret is never included in this response.
      operationId: listWebhooks
      tags:
      - Webhooks
      responses:
        '200':
          description: List of registered webhooks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListWebhooksResponse'
              example:
                webhooks:
                - id: 4f2c…
                  label: prod-workflows
                  url: https://api.yourapp.com/halliday/webhooks
                  event_types:
                  - WORKFLOW_COMPLETED
                  - WORKFLOW_FAILED
                  auth_header_name: null
                  created_at: '2026-06-20T12:00:00.000Z'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: Invalid API key
    patch:
      summary: Update a webhook
      description: 'Update a registered webhook. `label` identifies which webhook to update and cannot be changed.

        The editable fields are **`url`** and **`auth_header`** (pass `auth_header: null` to

        remove a previously set header). `event_types` is not updatable — to change which events a

        webhook subscribes to, delete it and create a new one. To change the signing secret, use the

        rotate-secret endpoint. A successful update returns `200` with an empty body.'
      operationId: updateWebhook
      tags:
      - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateWebhookRequest'
            example:
              label: prod-workflows
              url: https://api.yourapp.com/halliday/webhooks/v2
      responses:
        '200':
          description: Webhook updated successfully (empty body)
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
              example:
                errors:
                - code: invalid_type
                  expected: string
                  path:
                  - label
                  message: 'Invalid input: expected string, received undefined'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: Invalid API key
        '404':
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: No webhook with label 'prod-workflows' found
    delete:
      summary: Delete a webhook
      description: Delete a registered webhook, identified by its `label`.
      operationId: deleteWebhook
      tags:
      - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeleteWebhookRequest'
            example:
              label: prod-workflows
      responses:
        '200':
          description: Webhook deleted successfully (empty body)
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: Invalid API key
        '404':
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: No webhook with label 'prod-workflows' found
  /orgs/webhooks/rotate-secret:
    post:
      summary: Rotate the signing secret
      description: 'Generate a new signing secret for a webhook. The new key is added immediately and signs

        alongside any existing keys, so deliveries carry multiple `v1=` signatures during the overlap

        and you can roll the secret in your verifier without downtime.


        The previous keys are only phased out if you pass `retire_after` (floored to at least 24h from

        now). **Omit `retire_after` and the previous secret stays active indefinitely** — both keep

        signing. The response returns the webhook `id` and the new `signing_secret`; store it now, as

        it is not retrievable later.'
      operationId: rotateWebhookSecret
      tags:
      - Webhooks
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RotateSecretRequest'
            example:
              label: prod-workflows
              retire_after: '2026-07-01T00:00:00.000Z'
      responses:
        '200':
          description: New signing secret issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RotateSecretResponse'
              example:
                id: 4f2c…
                signing_secret: d4e5f6…
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
              example:
                errors:
                - code: invalid_type
                  expected: string
                  path:
                  - label
                  message: 'Invalid input: expected string, received undefined'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: Invalid API key
        '404':
          description: Webhook not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                errors:
                - kind: other
                  message: No webhook with label 'prod-workflows' found
webhooks:
  workflowStatusChanged:
    post:
      summary: Workflow status changed
      description: 'This is the HTTP `POST` request **Halliday sends to your registered `url`** when a workflow

        reaches a terminal state — it is not an endpoint you call. Respond with any `2xx` within the

        10-second timeout to acknowledge; anything else (or a timeout) is treated as a failed delivery

        and retried.


        ## Verifying the signature


        Each delivery carries an `X-Halliday-Signature` header: a comma-separated list of `v1=0x`

        values, where each `` is `HMAC-SHA256(raw_body)` computed with an active signing secret

        (used as a UTF-8 string), prefixed with `0x`. During a secret rotation more than one signature

        is present — treat the request as valid if **any** entry matches. Always verify against the

        **raw request body**, before JSON parsing.


        If you configured an `auth_header` when registering the webhook, Halliday also includes that

        custom header on every delivery — check it alongside the signature.


        ```js

        const crypto = require("crypto");


        function verifyHallidaySignature(rawBody, headerValue, signingSecret) {

        const expected = crypto

        .createHmac("sha256", signingSecret) // secret used as UTF-8 string

        .update(rawBody)

        .digest("hex");


        return headerValue

        .split(",")

        .map((s) => s.trim().replace(/^v1=/, "").replace(/^0x/, ""))

        .some((sig) =>

        sig.length === expected.length &&

        crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))

        );

        }

        ```


        ## Delivery & retry semantics


        - **Acknowledgement:** any `2xx` response within the 10-second timeout.

        - **Retries:** up to 12 attempts within a 24-hour window, after which the delivery is marked `FAILED`.

        - **Backoff:** exponential with jitter — roughly 1m, 5m, 15m, 1h, 2h, 4h, then 8h between attempts.

        - **Idempotency:** a given `(workflow_id, webhook, event_type)` is enqueued once. De-duplicate on

        the delivery `id`, since a retried delivery reuses the same `id`.'
      operationId: workflowStatusChanged
      tags:
      - Webhooks
      security: []
      parameters:
      - name: X-Halliday-Signature
        in: header
        required: true
        description: Comma-separated list of `v1=0x<hex>` HMAC-SHA256 signatures over the raw request body.
        schema:
          type: string
        example: v1=0x9fc719f93a352ffac9b6a7f5f360e77dccc957f2abaaeba6091a69c29f42520e
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookDelivery'
            example:
              id: 9555b9ed-1d0c-47ad-9e41-056fb4fe087e
              time: '2026-06-22T18:04:11.000Z'
              event_type: WORKFLOW_COMPLETED
              event:
                workflow_id: e050f684-7b3d-407a-98e7-5dfe11860317
                status: COMPLETE
      responses:
        '200':
          description: 'Return any `2xx` to acknowledge receipt. Any other status — or no response within 10

            seconds — is treated as a failed delivery and retried.

            '
components:
  schemas:
    RegisterWebhookRequest:
      type: object
      required:
      - url
      - label
      - event_types
      properties:
        url:
          type: string
          format: uri
          description: 'Receiver URL. Must be HTTPS on port 443. Private/reserved IP addresses are rejected (SSRF protection).

            '
          example: https://api.yourapp.com/halliday/webhooks
        label:
          type: string
          description: 'Human-readable identifier, unique per org. Used to address the webhook on update, rotate, and delete.

            '
          example: prod-workflows
        event_types:
          type: array
          minItems: 1
          description: At least one event type to subscribe to.
          items:
            $ref: '#/components/schemas/WebhookEventType'
          example:
          - WORKFLOW_COMPLETED
          - WORKFLOW_FAILED
        auth_header:
          $ref: '#/components/schemas/WebhookAuthHeader'
        signing_secret:
          type: string
          description: 'Optional. Provide your own HMAC-SHA256 signing secret instead of letting Halliday generate

            one. Omit this to receive a generated `signing_secret` in the response.

            '
          example: my-own-signing-secret
    RotateSecretRequest:
      type: object
      required:
      - label
      properties:
        label:
          type: string
          example: prod-workflows
        retire_after:
          type: string
          format: date-time
          description: 'Optional. When the previous signing secret(s) should stop signing. Floored to at least 24h

            from now. Omit to keep the previous secret active indefinitely (both keep signing).

            '
          example: '2026-07-01T00:00:00.000Z'
    OtherIssue:
      type: object
      required:
      - kind
      - message
      properties:
        kind:
          type: string
          enum:
          - other
        message:
          type: string
    UnknownIssue:
      type: object
      required:
      - kind
      - message
      properties:
        kind:
          type: string
          enum:
          - unknown
        message:
          type: string
    ValidationErrorResponse:
      type: object
      description: 'Returned when a request body fails schema validation. `errors` contains one entry per

        validation issue.

        '
      required:
      - errors
      properties:
        errors:
          type: array
          items:
            type: object
            required:
            - code
            - path
            - message
            properties:
              code:
                type: string
                description: Validation issue code (for example `invalid_type`).
                example: invalid_type
              expected:
                type: string
                description: Expected type. Present on type-mismatch issues.
                example: string
              path:
                type: array
                description: Path to the offending field in the request body.
                items:
                  type:
                  - string
                  - integer
                example:
                - label
              message:
                type: string
                example: 'Invalid input: expected string, received undefined'
    ListWebhooksResponse:
      type: object
      required:
      - webhooks
      properties:
        webhooks:
          type: array
          items:
            $ref: '#/components/schemas/WebhookSummary'
    WebhookEventType:
      type: string
      description: 'Event type a webhook can subscribe to. `WORKFLOW_COMPLETED` fires when a workflow''s status

        becomes `COMPLETE`; `WORKFLOW_FAILED` fires when it becomes `FAILED`.

        '
      enum:
      - WORKFLOW_COMPLETED
      - WORKFLOW_FAILED
    GeolocationIssue:
      type: object
      required:
      - kind
      - message
      properties:
        kind:
          type: string
          enum:
          - geolocation
        message:
          type: string
    AmountIssue:
      type: object
      required:
      - kind
      - asset
      - given
      - limits
      - source
      - message
      - reason
      properties:
        kind:
          type: string
          enum:
          - amount
        asset:
          $ref: '#/components/schemas/Asset'
        given:
          $ref: '#/components/schemas/Amount'
        limits:
          $ref: '#/components/schemas/Limits'
        downstream_limits:
          $ref: '#/components/schemas/Limits'
        source:
          type: string
        message:
          type: string
        reason:
          type: string
          enum:
          - TOO_LOW
          - TOO_HIGH
          - NO_VALID_AMOUNT
          - UNKNOWN
    Token:
      type: string
      description: Token identifier in the format "chain:address"
      pattern: ^[a-z]+:0x[a-fA-F0-9]+$
      example: ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    WebhookAuthHeader:
      type: object
      description: 'Optional custom HTTP header that Halliday attaches to every delivery to your receiver URL, so your

        endpoint can authenticate inbound deliveries in addition to verifying `X-Halliday-Signature`. The

        `value` is write-only — it is never returned by any endpoint; the list endpoint echoes only the

        header name as `auth_header_name`.

        '
      required:
      - name
      - value
      properties:
        name:
          type: string
          description: Header name Halliday sends on each delivery.
          example: X-Webhook-Token
        value:
          type: string
          description: Header value. Write-only — never returned.
          example: a-shared-secret-token
    Amount:
      type: string
      description: A decimal amount string.
    Issue:
      oneOf:
      - $ref: '#/components/schemas/AmountIssue'
      - $ref: '#/components/schemas/AmountDownstreamIssue'
      - $ref: '#/components/schemas/FundingIssue'
      - $ref: '#/components/schemas/OwnerIssue'
      - $ref: '#/components/schemas/GeolocationIssue'
      - $ref: '#/components/schemas/ProviderIssue'
      - $ref: '#/components/schemas/PayinMethodIssue'
      - $ref: '#/components/schemas/OtherIssue'
      - $ref: '#/components/schemas/UnknownIssue'
      discriminator:
        propertyName: kind
    Webhook:
      type: object
      description: 'A registered webhook including its signing secret. Returned only on creation and rotation.

        '
      required:
      - id
      - label
      - url
      - signing_secret
      properties:
        id:
          type: string
          example: 4f2c…
        label:
          type: string
          example: prod-workflows
        url:
          type: string
          format: uri
          example: https://api.yourapp.com/halliday/webhooks
        signing_secret:
          type: string
          description: 'HMAC-SHA256 signing secret. Store it now — it is never returned again from the list endpoint.

            '
          example: a1b2c3…
    FundingIssue:
      type: object
      required:
      - kind
      - token
      - balance
      properties:
        kind:
          type: string
          enum:
          - funding
        token:
          $ref: '#/components/schemas/Token'
        balance:
          type: string
          description: Current token balance at the funding address.
    WebhookDelivery:
      type: object
      description: The signed JSON payload Halliday POSTs to your receiver URL.
      required:
      - id
      - time
      - event_type
      - event
      properties:
        id:
          type: string
          format: uuid
          description: Unique delivery id. Use it for idempotency — a retried delivery reuses the same id.
          example: 9555b9ed-1d0c-47ad-9e41-056fb4fe087e
        time:
          type: string
          format: date-time
          description: ISO-8601 timestamp of the event.
          example: '2026-06-22T18:04:11.000Z'
        event_type:
          $ref: '#/components/schemas/WebhookEventType'
        event:
          type: object
          description: The terminal workflow state.
          required:
          - workflow_id
          - status
          properties:
            workflow_id:
              type: string
              format: uuid
              example: e050f684-7b3d-407a-98e7-5dfe11860317
            status:
              type: string
              description: Terminal workflow status.
              enum:
              - COMPLETE
              - FAILED
              example: COMPLETE
    PayinMethodIssue:
      type: object
      required:
      - kind
      - message
      properties:
        kind:
          type: string
          enum:
          - payin_method
        message:
          type: string
    OwnerIssue:
      type: object
      required:
      - kind
      - message
      - mitigation
      properties:
        kind:
          type: string
          enum:
          - owner
        message:
          type: string
        mitigation:
          type: string
          enum:
          - change
          - verify
    RotateSecretResponse:
      type: object
      required:
      - id
      - signing_secret
      properties:
        id:
          type: string
          example: 4f2c…
        signing_secret:
          type: string
          description: The new signing secret. Store it now — it is not retrievable later.
          example: d4e5f6…
    WebhookSummary:
      type: object
      description: A registered webhook as returned by the list endpoint. Never includes the signing secret.
      required:
      - id
      - label
      - url
      - event_types
      - auth_header_name
      - created_at
      properties:
        id:
          type: string
          format: uuid
          example: 4f2c8c1a-1b2c-4d3e-8f5a-6b7c8d9e0f12
        label:
          type: string
          example: prod-workflows
        url:
          type: string
          format: uri
          example: https://api.yourapp.com/halliday/webhooks
        event_types:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        auth_header_name:
          type:
          - string
          - 'null'
          description: 'Name of the custom delivery auth header, if one is configured (the value is never returned).

            `null` when no auth header is set.

            '
          example: null
        created_at:
          type: string
          format: date-time
          example: '2026-06-20T12:00:00.000Z'
    Limits:
      type: object
      properties:
        min:
          type: string
          description: Minimum amount as a decimal string.
        max:
          type: string
          description: Maximum amount as a decimal string.
    DeleteWebhookRequest:
      type: object
      required:
      - label
      properties:
        label:
          type: string
          example: prod-workflows
    ProviderIssue:
      type: object
      required:
      - kind
      - message
      properties:
        kind:
          type: string
          enum:
          - provider
        message:
          type: string
    UpdateWebhookRequest:
      type: object
      required:
      - label
      properties:
        label:
          type: string
          description: Identifies the webhook to update. Cannot be changed.
          example: prod-workflows
        url:
          type: string
          format: uri
          description: Optional. New receiver URL. Must be HTTPS on port 443.
          example: https://api.yourapp.com/halliday/webhooks/v2
        auth_header:
          description: 'Optional. Set or replace the custom delivery auth header, or pass `null` to remove an

            existing one.

            '
          anyOf:
          - $ref: '#/components/schemas/WebhookAuthHeader'
          - type: 'null'
    ErrorResponse:
      type: object
      required:
      - errors
      properties:
        errors:
          type: array
          items:
            $ref: '#/components/schemas/Issue'
      description: Error response for known errors
    Asset:
      type: string
      description: Identifier in the token format ("chain:address") or fiat currency code ("USD")
      example: ethereum:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    AmountDownstreamIssue:
      type: object
      required:
      - kind
      - asset
      - given
      - limits
      - source
      - message
      - reason
      properties:
        kind:
          type: string
          enum:
          - amount-downstream
        asset:
          $ref: '#/components/schemas/Asset'
        given:
          $ref: '#/components/schemas/Amount'
        limits:
          $ref: '#/components/schemas/Limits'
        downstream_limits:
          $ref: '#/components/schemas/Limits'
        source:
          type: string
        message:
          type: string
        reason:
          type: string
          enum:
          - TOO_LOW
          - TOO_HIGH
          - NO_VALID_AMOUNT
          - UNKNOWN
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API_KEY