asyncapi: 3.0.0
info:
  title: 1Funnel External Integration Webhooks
  version: 0.1.0-draft
  description: |
    Outbound HTTPS webhook notifications from 1Funnel to customer-managed
    endpoints (ADR-021 / ADR-011). This AsyncAPI document is the closed Phase
    5C event contract companion to the Phase 5B OpenAPI External Integration
    API. Runtime webhook delivery infrastructure is not claimed operational
    by this contract alone.

    ## Transport

    1Funnel delivers events as HTTPS `POST` requests to URLs registered via the
    webhook-endpoint management API. This contract describes only that public
    HTTPS push interface. Internal brokers, caches, workers, and queues are not
    part of the public integration surface.

    ## Envelope

    Exactly six required fields (DEC-ASYNC-ENV-01): `id`, `type`, `created_at`,
    `business_id`, `api_version` (constant `v1`), `data`.

    `created_at` is event-record creation time. `data.occurred_at` is business
    event time. Delivery-attempt timestamps are separate. Retries preserve the
    full logical event (id, type, created_at, business_id, api_version, data)
    and only change attempt metadata / signature.

    ## Delivery guarantees

    - HTTPS only
    - At-least-once delivery (exactly-once is not claimed)
    - No ordering guarantee; no sequence numbers or stream positions
      (DEC-ASYNC-ORD-01)
    - Exponential retries with jitter
    - Maximum retry window of 24 hours
    - HTTP 2xx indicates successful receipt by the consumer endpoint
    - Duplicate deliveries are possible; consumers must deduplicate on `id`
    - Event `id` remains stable across retries
    - Each delivery attempt uses a fresh timestamp and signature
    - Retry exhaustion yields terminal delivery status `failed_exhausted`
      (OpenAPI / DEC-WH-D01); dead-letter handling is internal

    ## Signing (DEC-WH-S01)

    See `docs/SIGNING.md` in this directory. Headers:

    - `1Funnel-Webhook-Id`
    - `1Funnel-Webhook-Timestamp`
    - `1Funnel-Webhook-Signature` (`v1=<lowercase_hex_hmac_sha256>`)

    Signed bytes: `timestamp + "." + raw_request_body`. Replay tolerance ±5
    minutes. Constant-time comparison required. Endpoint ownership verification
    and public secret rotation remain deferred (DEC-WH-V01 / DEC-WH-R01).

    ## Resource correlation

    Payloads are resource-reference-oriented event-time facts
    (DEC-ASYNC-SNAP-01). Correlate with REST using envelope `business_id`:

    - SMS: `/v1/businesses/{businessId}/sms/messages/{messageId}`
    - Bulk: `/v1/businesses/{businessId}/sms/bulk-messages/{bulkMessageId}`
    - OTP: `/v1/businesses/{businessId}/otp/challenges/{challengeId}`

    ## Compatibility

    See `docs/COMPATIBILITY.md` (DEC-ASYNC-COMPAT-01). Additive optional fields
    within `.v1` are compatible; breaking changes require a new event version
    and separate architectural approval.

    ## Consumer guidance

    See `docs/CONSUMER-GUIDE.md`.

    ## Publication

    Not mounted in `docs.json`. Non-GA draft (`0.1.0-draft`). Decisions closed
    in `docs/implementation/ASYNCAPI-DECISION-REGISTER.md`.
  license:
    name: Proprietary
  tags:
    - name: outbound-webhooks
      description: |
        Companion OpenAPI management contract:
        `openapi/external-integration-api/openapi.yaml` (Phase 5B baseline).
defaultContentType: application/json
channels:
  customerHttpsWebhook:
    address: '{consumerHttpsUrl}'
    title: Customer HTTPS webhook endpoint
    description: |
      Logical channel representing a customer-managed HTTPS URL registered as a
      webhook endpoint. 1Funnel sends JSON event envelopes to this URL. The
      concrete URL is chosen by the customer; it is not a 1Funnel hostname.
    parameters:
      consumerHttpsUrl:
        description: Absolute HTTPS URL of the customer webhook receiver.
    messages:
      smsMessageSentV1:
        name: sms.message.sent.v1
        title: SMS message sent
        summary: Provider submission accepted for an individual SMS message.
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: sms.message.sent.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for individual SMS
                        lifecycle events

                        (DEC-ASYNC-SMS-01, DEC-ASYNC-SNAP-01 — APPROVED).


                        Required fields only: `message_id`, `status`,
                        `occurred_at`.


                        This is an immutable event-time fact, not a live
                        resource mirror and not a

                        full `SmsMessage` representation. Retries deliver the
                        same logical payload.

                        Current resource state may differ; retrieve it via
                        authorized REST:


                        `GET
                        /v1/businesses/{businessId}/sms/messages/{messageId}`


                        using envelope `business_id` and `data.message_id`.


                        Semantics (ADR-018 SMS-12):

                        - `sent` — provider submission accepted; not delivery
                        confirmation.

                        - `delivered` — authoritative delivery outcome.

                        - `failed` — terminal failure display projection.


                        Public failure-reason taxonomy is deferred
                        (DEC-ASYNC-SMS-02). Do not expose

                        provider-specific error codes or descriptions. No
                        ordering metadata

                        (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Unknown properties may appear in later additive
                        revisions of `.v1`;

                        consumers must ignore unrecognized optional fields.
                      additionalProperties: true
                      required:
                        - message_id
                        - status
                        - occurred_at
                      properties:
                        message_id:
                          description: Public SMS message resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: >-
                            Merchant-visible display status at event time
                            (ADR-018 SMS-12).
                          enum:
                            - sent
                            - delivered
                            - failed
                        occurred_at:
                          description: >
                            When the underlying SMS lifecycle transition
                            occurred (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: sent
        examples:
          - name: smsMessageSentExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000001
              type: sms.message.sent.v1
              created_at: '2026-10-13T09:00:01.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                message_id: 018f3c4e-7b2a-7c3d-8e9f-aaaaaaaaaaaa
                status: sent
                occurred_at: '2026-10-13T09:00:01.000Z'
      smsMessageDeliveredV1:
        name: sms.message.delivered.v1
        title: SMS message delivered
        summary: Authoritative delivery outcome for an individual SMS message.
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: sms.message.delivered.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for individual SMS
                        lifecycle events

                        (DEC-ASYNC-SMS-01, DEC-ASYNC-SNAP-01 — APPROVED).


                        Required fields only: `message_id`, `status`,
                        `occurred_at`.


                        This is an immutable event-time fact, not a live
                        resource mirror and not a

                        full `SmsMessage` representation. Retries deliver the
                        same logical payload.

                        Current resource state may differ; retrieve it via
                        authorized REST:


                        `GET
                        /v1/businesses/{businessId}/sms/messages/{messageId}`


                        using envelope `business_id` and `data.message_id`.


                        Semantics (ADR-018 SMS-12):

                        - `sent` — provider submission accepted; not delivery
                        confirmation.

                        - `delivered` — authoritative delivery outcome.

                        - `failed` — terminal failure display projection.


                        Public failure-reason taxonomy is deferred
                        (DEC-ASYNC-SMS-02). Do not expose

                        provider-specific error codes or descriptions. No
                        ordering metadata

                        (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Unknown properties may appear in later additive
                        revisions of `.v1`;

                        consumers must ignore unrecognized optional fields.
                      additionalProperties: true
                      required:
                        - message_id
                        - status
                        - occurred_at
                      properties:
                        message_id:
                          description: Public SMS message resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: >-
                            Merchant-visible display status at event time
                            (ADR-018 SMS-12).
                          enum:
                            - sent
                            - delivered
                            - failed
                        occurred_at:
                          description: >
                            When the underlying SMS lifecycle transition
                            occurred (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: delivered
        examples:
          - name: smsMessageDeliveredExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffffffffff
              type: sms.message.delivered.v1
              created_at: '2026-10-13T09:00:05.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                message_id: 018f3c4e-7b2a-7c3d-8e9f-aaaaaaaaaaaa
                status: delivered
                occurred_at: '2026-10-13T09:00:05.000Z'
      smsMessageFailedV1:
        name: sms.message.failed.v1
        title: SMS message failed
        summary: Terminal failure display projection for an individual SMS message.
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: sms.message.failed.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for individual SMS
                        lifecycle events

                        (DEC-ASYNC-SMS-01, DEC-ASYNC-SNAP-01 — APPROVED).


                        Required fields only: `message_id`, `status`,
                        `occurred_at`.


                        This is an immutable event-time fact, not a live
                        resource mirror and not a

                        full `SmsMessage` representation. Retries deliver the
                        same logical payload.

                        Current resource state may differ; retrieve it via
                        authorized REST:


                        `GET
                        /v1/businesses/{businessId}/sms/messages/{messageId}`


                        using envelope `business_id` and `data.message_id`.


                        Semantics (ADR-018 SMS-12):

                        - `sent` — provider submission accepted; not delivery
                        confirmation.

                        - `delivered` — authoritative delivery outcome.

                        - `failed` — terminal failure display projection.


                        Public failure-reason taxonomy is deferred
                        (DEC-ASYNC-SMS-02). Do not expose

                        provider-specific error codes or descriptions. No
                        ordering metadata

                        (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Unknown properties may appear in later additive
                        revisions of `.v1`;

                        consumers must ignore unrecognized optional fields.
                      additionalProperties: true
                      required:
                        - message_id
                        - status
                        - occurred_at
                      properties:
                        message_id:
                          description: Public SMS message resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: >-
                            Merchant-visible display status at event time
                            (ADR-018 SMS-12).
                          enum:
                            - sent
                            - delivered
                            - failed
                        occurred_at:
                          description: >
                            When the underlying SMS lifecycle transition
                            occurred (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: failed
        examples:
          - name: smsMessageFailedExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000003
              type: sms.message.failed.v1
              created_at: '2026-10-13T09:00:08.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                message_id: 018f3c4e-7b2a-7c3d-8e9f-aaaaaaaaaa01
                status: failed
                occurred_at: '2026-10-13T09:00:08.000Z'
      smsBulkMessageProcessingV1:
        name: sms.bulk-message.processing.v1
        title: Bulk SMS processing
        summary: Bulk message entered the processing control-plane state.
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: sms.bulk-message.processing.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for bulk SMS
                        **processing** lifecycle

                        events (DEC-ASYNC-BULK-01, DEC-ASYNC-SNAP-01 —
                        APPROVED).


                        Required fields only: `bulk_message_id`, `status`,
                        `occurred_at`.


                        Processing status is distinct from individual recipient
                        delivery outcomes

                        (ADR-018 SMS-11–SMS-15):


                        - `processing` — bulk entered the processing
                        control-plane state.

                        - `completed` — primary processing pass finished. Does
                        **not** mean all
                          recipients received their messages. Retries may continue after completed.
                        - `failed` — pipeline/control-plane failure.


                        Excluded by approval (DEC-ASYNC-BULK-02,
                        DEC-ASYNC-BULK-03):

                        - `settled`

                        - `delivery_summary` and delivery reporting snapshots

                        - recipient outcome counters

                        - provider-specific diagnostics


                        Authoritative current resource state and delivery
                        reporting remain available

                        through authorized REST:


                        `GET
                        /v1/businesses/{businessId}/sms/bulk-messages/{bulkMessageId}`


                        using envelope `business_id` and `data.bulk_message_id`.


                        No ordering metadata (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Consumers must ignore unrecognized optional
                        fields added later

                        within `.v1`.
                      additionalProperties: true
                      required:
                        - bulk_message_id
                        - status
                        - occurred_at
                      properties:
                        bulk_message_id:
                          description: Public bulk SMS message resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: >-
                            Bulk processing status at event time (ADR-018
                            SMS-13).
                          enum:
                            - processing
                            - completed
                            - failed
                        occurred_at:
                          description: >
                            When the processing-lifecycle transition occurred
                            (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: processing
        examples:
          - name: smsBulkProcessingExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000010
              type: sms.bulk-message.processing.v1
              created_at: '2026-10-13T09:00:02.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                bulk_message_id: 018f3c4e-7b2a-7c3d-8e9f-bbbbbbbbbbbb
                status: processing
                occurred_at: '2026-10-13T09:00:02.000Z'
      smsBulkMessageCompletedV1:
        name: sms.bulk-message.completed.v1
        title: Bulk SMS processing completed
        summary: |
          Primary bulk processing pass finished. Not a claim that all recipients
          were delivered.
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: sms.bulk-message.completed.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for bulk SMS
                        **processing** lifecycle

                        events (DEC-ASYNC-BULK-01, DEC-ASYNC-SNAP-01 —
                        APPROVED).


                        Required fields only: `bulk_message_id`, `status`,
                        `occurred_at`.


                        Processing status is distinct from individual recipient
                        delivery outcomes

                        (ADR-018 SMS-11–SMS-15):


                        - `processing` — bulk entered the processing
                        control-plane state.

                        - `completed` — primary processing pass finished. Does
                        **not** mean all
                          recipients received their messages. Retries may continue after completed.
                        - `failed` — pipeline/control-plane failure.


                        Excluded by approval (DEC-ASYNC-BULK-02,
                        DEC-ASYNC-BULK-03):

                        - `settled`

                        - `delivery_summary` and delivery reporting snapshots

                        - recipient outcome counters

                        - provider-specific diagnostics


                        Authoritative current resource state and delivery
                        reporting remain available

                        through authorized REST:


                        `GET
                        /v1/businesses/{businessId}/sms/bulk-messages/{bulkMessageId}`


                        using envelope `business_id` and `data.bulk_message_id`.


                        No ordering metadata (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Consumers must ignore unrecognized optional
                        fields added later

                        within `.v1`.
                      additionalProperties: true
                      required:
                        - bulk_message_id
                        - status
                        - occurred_at
                      properties:
                        bulk_message_id:
                          description: Public bulk SMS message resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: >-
                            Bulk processing status at event time (ADR-018
                            SMS-13).
                          enum:
                            - processing
                            - completed
                            - failed
                        occurred_at:
                          description: >
                            When the processing-lifecycle transition occurred
                            (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: completed
        examples:
          - name: smsBulkCompletedExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000011
              type: sms.bulk-message.completed.v1
              created_at: '2026-10-13T09:05:00.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                bulk_message_id: 018f3c4e-7b2a-7c3d-8e9f-bbbbbbbbbbbb
                status: completed
                occurred_at: '2026-10-13T09:05:00.000Z'
      smsBulkMessageFailedV1:
        name: sms.bulk-message.failed.v1
        title: Bulk SMS processing failed
        summary: Bulk message pipeline/control-plane failure.
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: sms.bulk-message.failed.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for bulk SMS
                        **processing** lifecycle

                        events (DEC-ASYNC-BULK-01, DEC-ASYNC-SNAP-01 —
                        APPROVED).


                        Required fields only: `bulk_message_id`, `status`,
                        `occurred_at`.


                        Processing status is distinct from individual recipient
                        delivery outcomes

                        (ADR-018 SMS-11–SMS-15):


                        - `processing` — bulk entered the processing
                        control-plane state.

                        - `completed` — primary processing pass finished. Does
                        **not** mean all
                          recipients received their messages. Retries may continue after completed.
                        - `failed` — pipeline/control-plane failure.


                        Excluded by approval (DEC-ASYNC-BULK-02,
                        DEC-ASYNC-BULK-03):

                        - `settled`

                        - `delivery_summary` and delivery reporting snapshots

                        - recipient outcome counters

                        - provider-specific diagnostics


                        Authoritative current resource state and delivery
                        reporting remain available

                        through authorized REST:


                        `GET
                        /v1/businesses/{businessId}/sms/bulk-messages/{bulkMessageId}`


                        using envelope `business_id` and `data.bulk_message_id`.


                        No ordering metadata (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Consumers must ignore unrecognized optional
                        fields added later

                        within `.v1`.
                      additionalProperties: true
                      required:
                        - bulk_message_id
                        - status
                        - occurred_at
                      properties:
                        bulk_message_id:
                          description: Public bulk SMS message resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: >-
                            Bulk processing status at event time (ADR-018
                            SMS-13).
                          enum:
                            - processing
                            - completed
                            - failed
                        occurred_at:
                          description: >
                            When the processing-lifecycle transition occurred
                            (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: failed
        examples:
          - name: smsBulkFailedExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000012
              type: sms.bulk-message.failed.v1
              created_at: '2026-10-13T09:03:00.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                bulk_message_id: 018f3c4e-7b2a-7c3d-8e9f-bbbbbbbbbb01
                status: failed
                occurred_at: '2026-10-13T09:03:00.000Z'
      otpChallengeVerifiedV1:
        name: otp.challenge.verified.v1
        title: OTP challenge verified
        summary: Challenge successfully verified (verification lifecycle).
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: otp.challenge.verified.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for OTP challenge
                        verification-lifecycle

                        events (DEC-ASYNC-OTP-01, DEC-ASYNC-SNAP-01 — APPROVED).
                        Distinct from SMS

                        delivery state (ADR-020 OTP-A05).


                        Required fields only: `challenge_id`, `status`,
                        `occurred_at`.


                        Excluded by approval (DEC-ASYNC-OTP-02 and security
                        rules):

                        - `purpose`, `channel`

                        - OTP plaintext codes, hashes, peppers

                        - verification secrets / internal challenge secrets

                        - internal policy configuration

                        - destination phone numbers


                        Current challenge state (when still retrievable) is
                        available via authorized

                        REST:


                        `GET
                        /v1/businesses/{businessId}/otp/challenges/{challengeId}`


                        using envelope `business_id` and `data.challenge_id`.


                        No ordering metadata (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Consumers must ignore unrecognized optional
                        fields added later

                        within `.v1`.
                      additionalProperties: true
                      required:
                        - challenge_id
                        - status
                        - occurred_at
                      properties:
                        challenge_id:
                          description: Public OTP challenge resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: Public challenge lifecycle status at event time.
                          enum:
                            - verified
                            - expired
                            - locked
                        occurred_at:
                          description: >
                            When the verification-lifecycle transition occurred
                            (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: verified
        examples:
          - name: otpChallengeVerifiedExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000020
              type: otp.challenge.verified.v1
              created_at: '2026-10-13T09:01:10.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                challenge_id: 018f3c4e-7b2a-7c3d-8e9f-dddddddddddd
                status: verified
                occurred_at: '2026-10-13T09:01:10.000Z'
      otpChallengeExpiredV1:
        name: otp.challenge.expired.v1
        title: OTP challenge expired
        summary: Challenge expired without successful verification.
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: otp.challenge.expired.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for OTP challenge
                        verification-lifecycle

                        events (DEC-ASYNC-OTP-01, DEC-ASYNC-SNAP-01 — APPROVED).
                        Distinct from SMS

                        delivery state (ADR-020 OTP-A05).


                        Required fields only: `challenge_id`, `status`,
                        `occurred_at`.


                        Excluded by approval (DEC-ASYNC-OTP-02 and security
                        rules):

                        - `purpose`, `channel`

                        - OTP plaintext codes, hashes, peppers

                        - verification secrets / internal challenge secrets

                        - internal policy configuration

                        - destination phone numbers


                        Current challenge state (when still retrievable) is
                        available via authorized

                        REST:


                        `GET
                        /v1/businesses/{businessId}/otp/challenges/{challengeId}`


                        using envelope `business_id` and `data.challenge_id`.


                        No ordering metadata (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Consumers must ignore unrecognized optional
                        fields added later

                        within `.v1`.
                      additionalProperties: true
                      required:
                        - challenge_id
                        - status
                        - occurred_at
                      properties:
                        challenge_id:
                          description: Public OTP challenge resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: Public challenge lifecycle status at event time.
                          enum:
                            - verified
                            - expired
                            - locked
                        occurred_at:
                          description: >
                            When the verification-lifecycle transition occurred
                            (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: expired
        examples:
          - name: otpChallengeExpiredExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000021
              type: otp.challenge.expired.v1
              created_at: '2026-10-13T09:05:00.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                challenge_id: 018f3c4e-7b2a-7c3d-8e9f-dddddddddd01
                status: expired
                occurred_at: '2026-10-13T09:05:00.000Z'
      otpChallengeLockedV1:
        name: otp.challenge.locked.v1
        title: OTP challenge locked
        summary: Challenge locked after verification-attempt budget exhaustion.
        contentType: application/json
        headers:
          type: object
          description: DEC-WH-S01 delivery headers on every outbound POST.
          additionalProperties: false
          required:
            - 1Funnel-Webhook-Id
            - 1Funnel-Webhook-Timestamp
            - 1Funnel-Webhook-Signature
          properties:
            1Funnel-Webhook-Id:
              type: string
              description: Stable webhook event id (matches envelope `id`).
              format: uuid
            1Funnel-Webhook-Timestamp:
              type: string
              description: Unix epoch seconds as a decimal string.
              pattern: ^[0-9]+$
            1Funnel-Webhook-Signature:
              type: string
              description: v1=<lowercase_hex_hmac_sha256>
              pattern: ^v1=[0-9a-f]{64}$
              examples:
                - >-
                  v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
        payload:
          allOf:
            - type: object
              description: >
                CloudEvents-inspired public outbound webhook envelope (ADR-021
                WH-03 /

                ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
                CloudEvents

                compliance. Exactly six required fields — no additional envelope
                members.


                ## Timestamp semantics


                - `created_at` — when the event record was created (RFC 3339
                UTC).

                - `data.occurred_at` — when the underlying business event
                occurred
                  (RFC 3339 UTC). May differ from `created_at`.
                - Delivery-attempt timestamps are separate (signing header /
                delivery
                  records) and are not envelope fields.

                ## Retry invariants


                Across delivery retries the following are immutable:


                - `id`, `type`, `created_at`, `business_id`, `api_version`, and
                `data`


                Only delivery-attempt metadata may change. Each retry receives a
                fresh

                `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
                (DEC-WH-S01).
              additionalProperties: false
              required:
                - id
                - type
                - created_at
                - business_id
                - api_version
                - data
              properties:
                id:
                  description: Stable logical event id. Retries preserve this id.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                type:
                  type: string
                  description: >
                    Approved v1 outbound webhook event types (ADR-021 WH-02).
                    Exactly nine

                    public types. Wallet and payment events are deferred.
                  enum:
                    - sms.message.sent.v1
                    - sms.message.delivered.v1
                    - sms.message.failed.v1
                    - sms.bulk-message.processing.v1
                    - sms.bulk-message.completed.v1
                    - sms.bulk-message.failed.v1
                    - otp.challenge.verified.v1
                    - otp.challenge.expired.v1
                    - otp.challenge.locked.v1
                  examples:
                    - sms.message.delivered.v1
                created_at:
                  description: Event-record creation timestamp (RFC 3339 UTC).
                  type: string
                  format: date-time
                  examples:
                    - '2026-10-13T09:00:00.000Z'
                business_id:
                  description: >
                    Business that owns the event. Use with resource ids in
                    `data` to

                    correlate authorized REST lookups under

                    `/v1/businesses/{businessId}/...`.
                  type: string
                  format: uuid
                  pattern: >-
                    ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                  examples:
                    - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                api_version:
                  type: string
                  description: >
                    Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                    Constant `v1`.

                    Distinct from the event-type suffix (`.v1`) and from OpenAPI
                    REST path

                    prefixes. Delivery retries preserve this value.
                  const: v1
                  examples:
                    - v1
                data:
                  type: object
                  description: Event-specific payload (typed per message).
            - type: object
              required:
                - type
                - data
              properties:
                type:
                  type: string
                  const: otp.challenge.locked.v1
                data:
                  allOf:
                    - type: object
                      description: >
                        Resource-reference-oriented payload for OTP challenge
                        verification-lifecycle

                        events (DEC-ASYNC-OTP-01, DEC-ASYNC-SNAP-01 — APPROVED).
                        Distinct from SMS

                        delivery state (ADR-020 OTP-A05).


                        Required fields only: `challenge_id`, `status`,
                        `occurred_at`.


                        Excluded by approval (DEC-ASYNC-OTP-02 and security
                        rules):

                        - `purpose`, `channel`

                        - OTP plaintext codes, hashes, peppers

                        - verification secrets / internal challenge secrets

                        - internal policy configuration

                        - destination phone numbers


                        Current challenge state (when still retrievable) is
                        available via authorized

                        REST:


                        `GET
                        /v1/businesses/{businessId}/otp/challenges/{challengeId}`


                        using envelope `business_id` and `data.challenge_id`.


                        No ordering metadata (DEC-ASYNC-ORD-01).


                        ## Extensibility (DEC-ASYNC-COMPAT-01)


                        Producer contract: required fields, types, and approved
                        status values are

                        strict. Consumers must ignore unrecognized optional
                        fields added later

                        within `.v1`.
                      additionalProperties: true
                      required:
                        - challenge_id
                        - status
                        - occurred_at
                      properties:
                        challenge_id:
                          description: Public OTP challenge resource id.
                          type: string
                          format: uuid
                          pattern: >-
                            ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                          examples:
                            - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                        status:
                          type: string
                          description: Public challenge lifecycle status at event time.
                          enum:
                            - verified
                            - expired
                            - locked
                        occurred_at:
                          description: >
                            When the verification-lifecycle transition occurred
                            (RFC 3339 UTC).

                            Distinct from envelope `created_at` and from
                            delivery-attempt time.
                          type: string
                          format: date-time
                          examples:
                            - '2026-10-13T09:00:00.000Z'
                    - type: object
                      required:
                        - status
                      properties:
                        status:
                          type: string
                          const: locked
        examples:
          - name: otpChallengeLockedExample
            payload:
              id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000022
              type: otp.challenge.locked.v1
              created_at: '2026-10-13T09:02:00.000Z'
              business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version: v1
              data:
                challenge_id: 018f3c4e-7b2a-7c3d-8e9f-dddddddddd02
                status: locked
                occurred_at: '2026-10-13T09:02:00.000Z'
    bindings:
      http:
        method: POST
operations:
  sendWebhookEvent:
    action: send
    title: Deliver outbound webhook event
    description: |
      1Funnel sends one of the nine approved event messages to the customer
      HTTPS endpoint. Ordering is not guaranteed. Consumers must verify
      signatures and process idempotently.
    channel:
      $ref: '#/channels/customerHttpsWebhook'
    messages:
      - $ref: '#/channels/customerHttpsWebhook/messages/smsMessageSentV1'
      - $ref: '#/channels/customerHttpsWebhook/messages/smsMessageDeliveredV1'
      - $ref: '#/channels/customerHttpsWebhook/messages/smsMessageFailedV1'
      - $ref: '#/channels/customerHttpsWebhook/messages/smsBulkMessageProcessingV1'
      - $ref: '#/channels/customerHttpsWebhook/messages/smsBulkMessageCompletedV1'
      - $ref: '#/channels/customerHttpsWebhook/messages/smsBulkMessageFailedV1'
      - $ref: '#/channels/customerHttpsWebhook/messages/otpChallengeVerifiedV1'
      - $ref: '#/channels/customerHttpsWebhook/messages/otpChallengeExpiredV1'
      - $ref: '#/channels/customerHttpsWebhook/messages/otpChallengeLockedV1'
components:
  messages:
    SmsMessageSentV1:
      name: sms.message.sent.v1
      title: SMS message sent
      summary: Provider submission accepted for an individual SMS message.
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: sms.message.sent.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for individual SMS
                      lifecycle events

                      (DEC-ASYNC-SMS-01, DEC-ASYNC-SNAP-01 — APPROVED).


                      Required fields only: `message_id`, `status`,
                      `occurred_at`.


                      This is an immutable event-time fact, not a live resource
                      mirror and not a

                      full `SmsMessage` representation. Retries deliver the same
                      logical payload.

                      Current resource state may differ; retrieve it via
                      authorized REST:


                      `GET /v1/businesses/{businessId}/sms/messages/{messageId}`


                      using envelope `business_id` and `data.message_id`.


                      Semantics (ADR-018 SMS-12):

                      - `sent` — provider submission accepted; not delivery
                      confirmation.

                      - `delivered` — authoritative delivery outcome.

                      - `failed` — terminal failure display projection.


                      Public failure-reason taxonomy is deferred
                      (DEC-ASYNC-SMS-02). Do not expose

                      provider-specific error codes or descriptions. No ordering
                      metadata

                      (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Unknown properties may appear in later additive
                      revisions of `.v1`;

                      consumers must ignore unrecognized optional fields.
                    additionalProperties: true
                    required:
                      - message_id
                      - status
                      - occurred_at
                    properties:
                      message_id:
                        description: Public SMS message resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: >-
                          Merchant-visible display status at event time (ADR-018
                          SMS-12).
                        enum:
                          - sent
                          - delivered
                          - failed
                      occurred_at:
                        description: >
                          When the underlying SMS lifecycle transition occurred
                          (RFC 3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: sent
      examples:
        - name: smsMessageSentExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000001
            type: sms.message.sent.v1
            created_at: '2026-10-13T09:00:01.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              message_id: 018f3c4e-7b2a-7c3d-8e9f-aaaaaaaaaaaa
              status: sent
              occurred_at: '2026-10-13T09:00:01.000Z'
    SmsMessageDeliveredV1:
      name: sms.message.delivered.v1
      title: SMS message delivered
      summary: Authoritative delivery outcome for an individual SMS message.
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: sms.message.delivered.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for individual SMS
                      lifecycle events

                      (DEC-ASYNC-SMS-01, DEC-ASYNC-SNAP-01 — APPROVED).


                      Required fields only: `message_id`, `status`,
                      `occurred_at`.


                      This is an immutable event-time fact, not a live resource
                      mirror and not a

                      full `SmsMessage` representation. Retries deliver the same
                      logical payload.

                      Current resource state may differ; retrieve it via
                      authorized REST:


                      `GET /v1/businesses/{businessId}/sms/messages/{messageId}`


                      using envelope `business_id` and `data.message_id`.


                      Semantics (ADR-018 SMS-12):

                      - `sent` — provider submission accepted; not delivery
                      confirmation.

                      - `delivered` — authoritative delivery outcome.

                      - `failed` — terminal failure display projection.


                      Public failure-reason taxonomy is deferred
                      (DEC-ASYNC-SMS-02). Do not expose

                      provider-specific error codes or descriptions. No ordering
                      metadata

                      (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Unknown properties may appear in later additive
                      revisions of `.v1`;

                      consumers must ignore unrecognized optional fields.
                    additionalProperties: true
                    required:
                      - message_id
                      - status
                      - occurred_at
                    properties:
                      message_id:
                        description: Public SMS message resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: >-
                          Merchant-visible display status at event time (ADR-018
                          SMS-12).
                        enum:
                          - sent
                          - delivered
                          - failed
                      occurred_at:
                        description: >
                          When the underlying SMS lifecycle transition occurred
                          (RFC 3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: delivered
      examples:
        - name: smsMessageDeliveredExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffffffffff
            type: sms.message.delivered.v1
            created_at: '2026-10-13T09:00:05.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              message_id: 018f3c4e-7b2a-7c3d-8e9f-aaaaaaaaaaaa
              status: delivered
              occurred_at: '2026-10-13T09:00:05.000Z'
    SmsMessageFailedV1:
      name: sms.message.failed.v1
      title: SMS message failed
      summary: Terminal failure display projection for an individual SMS message.
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: sms.message.failed.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for individual SMS
                      lifecycle events

                      (DEC-ASYNC-SMS-01, DEC-ASYNC-SNAP-01 — APPROVED).


                      Required fields only: `message_id`, `status`,
                      `occurred_at`.


                      This is an immutable event-time fact, not a live resource
                      mirror and not a

                      full `SmsMessage` representation. Retries deliver the same
                      logical payload.

                      Current resource state may differ; retrieve it via
                      authorized REST:


                      `GET /v1/businesses/{businessId}/sms/messages/{messageId}`


                      using envelope `business_id` and `data.message_id`.


                      Semantics (ADR-018 SMS-12):

                      - `sent` — provider submission accepted; not delivery
                      confirmation.

                      - `delivered` — authoritative delivery outcome.

                      - `failed` — terminal failure display projection.


                      Public failure-reason taxonomy is deferred
                      (DEC-ASYNC-SMS-02). Do not expose

                      provider-specific error codes or descriptions. No ordering
                      metadata

                      (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Unknown properties may appear in later additive
                      revisions of `.v1`;

                      consumers must ignore unrecognized optional fields.
                    additionalProperties: true
                    required:
                      - message_id
                      - status
                      - occurred_at
                    properties:
                      message_id:
                        description: Public SMS message resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: >-
                          Merchant-visible display status at event time (ADR-018
                          SMS-12).
                        enum:
                          - sent
                          - delivered
                          - failed
                      occurred_at:
                        description: >
                          When the underlying SMS lifecycle transition occurred
                          (RFC 3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: failed
      examples:
        - name: smsMessageFailedExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000003
            type: sms.message.failed.v1
            created_at: '2026-10-13T09:00:08.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              message_id: 018f3c4e-7b2a-7c3d-8e9f-aaaaaaaaaa01
              status: failed
              occurred_at: '2026-10-13T09:00:08.000Z'
    SmsBulkMessageProcessingV1:
      name: sms.bulk-message.processing.v1
      title: Bulk SMS processing
      summary: Bulk message entered the processing control-plane state.
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: sms.bulk-message.processing.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for bulk SMS
                      **processing** lifecycle

                      events (DEC-ASYNC-BULK-01, DEC-ASYNC-SNAP-01 — APPROVED).


                      Required fields only: `bulk_message_id`, `status`,
                      `occurred_at`.


                      Processing status is distinct from individual recipient
                      delivery outcomes

                      (ADR-018 SMS-11–SMS-15):


                      - `processing` — bulk entered the processing control-plane
                      state.

                      - `completed` — primary processing pass finished. Does
                      **not** mean all
                        recipients received their messages. Retries may continue after completed.
                      - `failed` — pipeline/control-plane failure.


                      Excluded by approval (DEC-ASYNC-BULK-02,
                      DEC-ASYNC-BULK-03):

                      - `settled`

                      - `delivery_summary` and delivery reporting snapshots

                      - recipient outcome counters

                      - provider-specific diagnostics


                      Authoritative current resource state and delivery
                      reporting remain available

                      through authorized REST:


                      `GET
                      /v1/businesses/{businessId}/sms/bulk-messages/{bulkMessageId}`


                      using envelope `business_id` and `data.bulk_message_id`.


                      No ordering metadata (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Consumers must ignore unrecognized optional fields
                      added later

                      within `.v1`.
                    additionalProperties: true
                    required:
                      - bulk_message_id
                      - status
                      - occurred_at
                    properties:
                      bulk_message_id:
                        description: Public bulk SMS message resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: Bulk processing status at event time (ADR-018 SMS-13).
                        enum:
                          - processing
                          - completed
                          - failed
                      occurred_at:
                        description: >
                          When the processing-lifecycle transition occurred (RFC
                          3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: processing
      examples:
        - name: smsBulkProcessingExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000010
            type: sms.bulk-message.processing.v1
            created_at: '2026-10-13T09:00:02.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              bulk_message_id: 018f3c4e-7b2a-7c3d-8e9f-bbbbbbbbbbbb
              status: processing
              occurred_at: '2026-10-13T09:00:02.000Z'
    SmsBulkMessageCompletedV1:
      name: sms.bulk-message.completed.v1
      title: Bulk SMS processing completed
      summary: |
        Primary bulk processing pass finished. Not a claim that all recipients
        were delivered.
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: sms.bulk-message.completed.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for bulk SMS
                      **processing** lifecycle

                      events (DEC-ASYNC-BULK-01, DEC-ASYNC-SNAP-01 — APPROVED).


                      Required fields only: `bulk_message_id`, `status`,
                      `occurred_at`.


                      Processing status is distinct from individual recipient
                      delivery outcomes

                      (ADR-018 SMS-11–SMS-15):


                      - `processing` — bulk entered the processing control-plane
                      state.

                      - `completed` — primary processing pass finished. Does
                      **not** mean all
                        recipients received their messages. Retries may continue after completed.
                      - `failed` — pipeline/control-plane failure.


                      Excluded by approval (DEC-ASYNC-BULK-02,
                      DEC-ASYNC-BULK-03):

                      - `settled`

                      - `delivery_summary` and delivery reporting snapshots

                      - recipient outcome counters

                      - provider-specific diagnostics


                      Authoritative current resource state and delivery
                      reporting remain available

                      through authorized REST:


                      `GET
                      /v1/businesses/{businessId}/sms/bulk-messages/{bulkMessageId}`


                      using envelope `business_id` and `data.bulk_message_id`.


                      No ordering metadata (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Consumers must ignore unrecognized optional fields
                      added later

                      within `.v1`.
                    additionalProperties: true
                    required:
                      - bulk_message_id
                      - status
                      - occurred_at
                    properties:
                      bulk_message_id:
                        description: Public bulk SMS message resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: Bulk processing status at event time (ADR-018 SMS-13).
                        enum:
                          - processing
                          - completed
                          - failed
                      occurred_at:
                        description: >
                          When the processing-lifecycle transition occurred (RFC
                          3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: completed
      examples:
        - name: smsBulkCompletedExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000011
            type: sms.bulk-message.completed.v1
            created_at: '2026-10-13T09:05:00.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              bulk_message_id: 018f3c4e-7b2a-7c3d-8e9f-bbbbbbbbbbbb
              status: completed
              occurred_at: '2026-10-13T09:05:00.000Z'
    SmsBulkMessageFailedV1:
      name: sms.bulk-message.failed.v1
      title: Bulk SMS processing failed
      summary: Bulk message pipeline/control-plane failure.
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: sms.bulk-message.failed.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for bulk SMS
                      **processing** lifecycle

                      events (DEC-ASYNC-BULK-01, DEC-ASYNC-SNAP-01 — APPROVED).


                      Required fields only: `bulk_message_id`, `status`,
                      `occurred_at`.


                      Processing status is distinct from individual recipient
                      delivery outcomes

                      (ADR-018 SMS-11–SMS-15):


                      - `processing` — bulk entered the processing control-plane
                      state.

                      - `completed` — primary processing pass finished. Does
                      **not** mean all
                        recipients received their messages. Retries may continue after completed.
                      - `failed` — pipeline/control-plane failure.


                      Excluded by approval (DEC-ASYNC-BULK-02,
                      DEC-ASYNC-BULK-03):

                      - `settled`

                      - `delivery_summary` and delivery reporting snapshots

                      - recipient outcome counters

                      - provider-specific diagnostics


                      Authoritative current resource state and delivery
                      reporting remain available

                      through authorized REST:


                      `GET
                      /v1/businesses/{businessId}/sms/bulk-messages/{bulkMessageId}`


                      using envelope `business_id` and `data.bulk_message_id`.


                      No ordering metadata (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Consumers must ignore unrecognized optional fields
                      added later

                      within `.v1`.
                    additionalProperties: true
                    required:
                      - bulk_message_id
                      - status
                      - occurred_at
                    properties:
                      bulk_message_id:
                        description: Public bulk SMS message resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: Bulk processing status at event time (ADR-018 SMS-13).
                        enum:
                          - processing
                          - completed
                          - failed
                      occurred_at:
                        description: >
                          When the processing-lifecycle transition occurred (RFC
                          3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: failed
      examples:
        - name: smsBulkFailedExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000012
            type: sms.bulk-message.failed.v1
            created_at: '2026-10-13T09:03:00.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              bulk_message_id: 018f3c4e-7b2a-7c3d-8e9f-bbbbbbbbbb01
              status: failed
              occurred_at: '2026-10-13T09:03:00.000Z'
    OtpChallengeVerifiedV1:
      name: otp.challenge.verified.v1
      title: OTP challenge verified
      summary: Challenge successfully verified (verification lifecycle).
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: otp.challenge.verified.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for OTP challenge
                      verification-lifecycle

                      events (DEC-ASYNC-OTP-01, DEC-ASYNC-SNAP-01 — APPROVED).
                      Distinct from SMS

                      delivery state (ADR-020 OTP-A05).


                      Required fields only: `challenge_id`, `status`,
                      `occurred_at`.


                      Excluded by approval (DEC-ASYNC-OTP-02 and security
                      rules):

                      - `purpose`, `channel`

                      - OTP plaintext codes, hashes, peppers

                      - verification secrets / internal challenge secrets

                      - internal policy configuration

                      - destination phone numbers


                      Current challenge state (when still retrievable) is
                      available via authorized

                      REST:


                      `GET
                      /v1/businesses/{businessId}/otp/challenges/{challengeId}`


                      using envelope `business_id` and `data.challenge_id`.


                      No ordering metadata (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Consumers must ignore unrecognized optional fields
                      added later

                      within `.v1`.
                    additionalProperties: true
                    required:
                      - challenge_id
                      - status
                      - occurred_at
                    properties:
                      challenge_id:
                        description: Public OTP challenge resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: Public challenge lifecycle status at event time.
                        enum:
                          - verified
                          - expired
                          - locked
                      occurred_at:
                        description: >
                          When the verification-lifecycle transition occurred
                          (RFC 3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: verified
      examples:
        - name: otpChallengeVerifiedExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000020
            type: otp.challenge.verified.v1
            created_at: '2026-10-13T09:01:10.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              challenge_id: 018f3c4e-7b2a-7c3d-8e9f-dddddddddddd
              status: verified
              occurred_at: '2026-10-13T09:01:10.000Z'
    OtpChallengeExpiredV1:
      name: otp.challenge.expired.v1
      title: OTP challenge expired
      summary: Challenge expired without successful verification.
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: otp.challenge.expired.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for OTP challenge
                      verification-lifecycle

                      events (DEC-ASYNC-OTP-01, DEC-ASYNC-SNAP-01 — APPROVED).
                      Distinct from SMS

                      delivery state (ADR-020 OTP-A05).


                      Required fields only: `challenge_id`, `status`,
                      `occurred_at`.


                      Excluded by approval (DEC-ASYNC-OTP-02 and security
                      rules):

                      - `purpose`, `channel`

                      - OTP plaintext codes, hashes, peppers

                      - verification secrets / internal challenge secrets

                      - internal policy configuration

                      - destination phone numbers


                      Current challenge state (when still retrievable) is
                      available via authorized

                      REST:


                      `GET
                      /v1/businesses/{businessId}/otp/challenges/{challengeId}`


                      using envelope `business_id` and `data.challenge_id`.


                      No ordering metadata (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Consumers must ignore unrecognized optional fields
                      added later

                      within `.v1`.
                    additionalProperties: true
                    required:
                      - challenge_id
                      - status
                      - occurred_at
                    properties:
                      challenge_id:
                        description: Public OTP challenge resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: Public challenge lifecycle status at event time.
                        enum:
                          - verified
                          - expired
                          - locked
                      occurred_at:
                        description: >
                          When the verification-lifecycle transition occurred
                          (RFC 3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: expired
      examples:
        - name: otpChallengeExpiredExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000021
            type: otp.challenge.expired.v1
            created_at: '2026-10-13T09:05:00.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              challenge_id: 018f3c4e-7b2a-7c3d-8e9f-dddddddddd01
              status: expired
              occurred_at: '2026-10-13T09:05:00.000Z'
    OtpChallengeLockedV1:
      name: otp.challenge.locked.v1
      title: OTP challenge locked
      summary: Challenge locked after verification-attempt budget exhaustion.
      contentType: application/json
      headers:
        type: object
        description: DEC-WH-S01 delivery headers on every outbound POST.
        additionalProperties: false
        required:
          - 1Funnel-Webhook-Id
          - 1Funnel-Webhook-Timestamp
          - 1Funnel-Webhook-Signature
        properties:
          1Funnel-Webhook-Id:
            type: string
            description: Stable webhook event id (matches envelope `id`).
            format: uuid
          1Funnel-Webhook-Timestamp:
            type: string
            description: Unix epoch seconds as a decimal string.
            pattern: ^[0-9]+$
          1Funnel-Webhook-Signature:
            type: string
            description: v1=<lowercase_hex_hmac_sha256>
            pattern: ^v1=[0-9a-f]{64}$
            examples:
              - >-
                v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
      payload:
        allOf:
          - type: object
            description: >
              CloudEvents-inspired public outbound webhook envelope (ADR-021
              WH-03 /

              ADR-017 SECOP-10 / DEC-ASYNC-ENV-01). Not a claim of full
              CloudEvents

              compliance. Exactly six required fields — no additional envelope
              members.


              ## Timestamp semantics


              - `created_at` — when the event record was created (RFC 3339 UTC).

              - `data.occurred_at` — when the underlying business event occurred
                (RFC 3339 UTC). May differ from `created_at`.
              - Delivery-attempt timestamps are separate (signing header /
              delivery
                records) and are not envelope fields.

              ## Retry invariants


              Across delivery retries the following are immutable:


              - `id`, `type`, `created_at`, `business_id`, `api_version`, and
              `data`


              Only delivery-attempt metadata may change. Each retry receives a
              fresh

              `1Funnel-Webhook-Timestamp` and `1Funnel-Webhook-Signature`
              (DEC-WH-S01).
            additionalProperties: false
            required:
              - id
              - type
              - created_at
              - business_id
              - api_version
              - data
            properties:
              id:
                description: Stable logical event id. Retries preserve this id.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              type:
                type: string
                description: >
                  Approved v1 outbound webhook event types (ADR-021 WH-02).
                  Exactly nine

                  public types. Wallet and payment events are deferred.
                enum:
                  - sms.message.sent.v1
                  - sms.message.delivered.v1
                  - sms.message.failed.v1
                  - sms.bulk-message.processing.v1
                  - sms.bulk-message.completed.v1
                  - sms.bulk-message.failed.v1
                  - otp.challenge.verified.v1
                  - otp.challenge.expired.v1
                  - otp.challenge.locked.v1
                examples:
                  - sms.message.delivered.v1
              created_at:
                description: Event-record creation timestamp (RFC 3339 UTC).
                type: string
                format: date-time
                examples:
                  - '2026-10-13T09:00:00.000Z'
              business_id:
                description: >
                  Business that owns the event. Use with resource ids in `data`
                  to

                  correlate authorized REST lookups under

                  `/v1/businesses/{businessId}/...`.
                type: string
                format: uuid
                pattern: >-
                  ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                examples:
                  - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
              api_version:
                type: string
                description: >
                  Event envelope API version (DEC-ASYNC-ENV-02 — APPROVED).
                  Constant `v1`.

                  Distinct from the event-type suffix (`.v1`) and from OpenAPI
                  REST path

                  prefixes. Delivery retries preserve this value.
                const: v1
                examples:
                  - v1
              data:
                type: object
                description: Event-specific payload (typed per message).
          - type: object
            required:
              - type
              - data
            properties:
              type:
                type: string
                const: otp.challenge.locked.v1
              data:
                allOf:
                  - type: object
                    description: >
                      Resource-reference-oriented payload for OTP challenge
                      verification-lifecycle

                      events (DEC-ASYNC-OTP-01, DEC-ASYNC-SNAP-01 — APPROVED).
                      Distinct from SMS

                      delivery state (ADR-020 OTP-A05).


                      Required fields only: `challenge_id`, `status`,
                      `occurred_at`.


                      Excluded by approval (DEC-ASYNC-OTP-02 and security
                      rules):

                      - `purpose`, `channel`

                      - OTP plaintext codes, hashes, peppers

                      - verification secrets / internal challenge secrets

                      - internal policy configuration

                      - destination phone numbers


                      Current challenge state (when still retrievable) is
                      available via authorized

                      REST:


                      `GET
                      /v1/businesses/{businessId}/otp/challenges/{challengeId}`


                      using envelope `business_id` and `data.challenge_id`.


                      No ordering metadata (DEC-ASYNC-ORD-01).


                      ## Extensibility (DEC-ASYNC-COMPAT-01)


                      Producer contract: required fields, types, and approved
                      status values are

                      strict. Consumers must ignore unrecognized optional fields
                      added later

                      within `.v1`.
                    additionalProperties: true
                    required:
                      - challenge_id
                      - status
                      - occurred_at
                    properties:
                      challenge_id:
                        description: Public OTP challenge resource id.
                        type: string
                        format: uuid
                        pattern: >-
                          ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$
                        examples:
                          - 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
                      status:
                        type: string
                        description: Public challenge lifecycle status at event time.
                        enum:
                          - verified
                          - expired
                          - locked
                      occurred_at:
                        description: >
                          When the verification-lifecycle transition occurred
                          (RFC 3339 UTC).

                          Distinct from envelope `created_at` and from
                          delivery-attempt time.
                        type: string
                        format: date-time
                        examples:
                          - '2026-10-13T09:00:00.000Z'
                  - type: object
                    required:
                      - status
                    properties:
                      status:
                        type: string
                        const: locked
      examples:
        - name: otpChallengeLockedExample
          payload:
            id: 018f3c4e-7b2a-7c3d-8e9f-ffffff000022
            type: otp.challenge.locked.v1
            created_at: '2026-10-13T09:02:00.000Z'
            business_id: 018f3c4e-7b2a-7c3d-8e9f-0123456789ab
            api_version: v1
            data:
              challenge_id: 018f3c4e-7b2a-7c3d-8e9f-dddddddddd02
              status: locked
              occurred_at: '2026-10-13T09:02:00.000Z'
  schemas:
    WebhookDeliveryHeaders:
      type: object
      description: DEC-WH-S01 delivery headers on every outbound POST.
      additionalProperties: false
      required:
        - 1Funnel-Webhook-Id
        - 1Funnel-Webhook-Timestamp
        - 1Funnel-Webhook-Signature
      properties:
        1Funnel-Webhook-Id:
          type: string
          description: Stable webhook event id (matches envelope `id`).
          format: uuid
        1Funnel-Webhook-Timestamp:
          type: string
          description: Unix epoch seconds as a decimal string.
          pattern: ^[0-9]+$
        1Funnel-Webhook-Signature:
          type: string
          description: v1=<lowercase_hex_hmac_sha256>
          pattern: ^v1=[0-9a-f]{64}$
          examples:
            - >-
              v1=18fed3ff2ee561c105312f1af294d1360b34a8e3663aa0695bd3eda69ac6cb9c
