---
components:
  responses:
    BadRequest:
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
      description: Missing or invalid fields
    Conflict:
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
      description: Conflicting state (e.g. already paired)
    InternalError:
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
      description: Internal failure
    NotFound:
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
      description: Not found
    ServiceUnavailable:
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
      description: WhatsApp session is not connected; pair first or retry later
    Unauthorized:
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
      description: Missing or invalid bearer token
    UpstreamError:
      content:
        application/json:
          schema:
            "$ref": "#/components/schemas/Error"
      description: WhatsApp upstream request failed
  schemas:
    CallIncomingWebhook:
      properties:
        call_id:
          type: string
        contact_id:
          type: string
        event:
          enum:
          - call_incoming
          examples:
          - call_incoming
          type: string
        platform:
          examples:
          - android
          type: string
        timestamp:
          format: int64
          type: integer
      required:
      - event
      - call_id
      - contact_id
      - platform
      - timestamp
      type: object
    CallTerminatedWebhook:
      properties:
        call_id:
          type: string
        contact_id:
          type: string
        event:
          enum:
          - call_terminated
          examples:
          - call_terminated
          type: string
        reason:
          enum:
          - timeout
          - hangup
          - decline
          - busy
          - accept_elsewhere
          type: string
        timestamp:
          format: int64
          type: integer
      required:
      - event
      - call_id
      - contact_id
      - reason
      - timestamp
      type: object
    ConnectedWebhook:
      properties:
        event:
          enum:
          - connected
          examples:
          - connected
          type: string
        phone:
          type: string
      required:
      - event
      - phone
      type: object
    ContactCheckEntry:
      properties:
        is_business:
          type: boolean
        is_on_whatsapp:
          description: Deprecated alias of registered, kept for backward compatibility.
            Use registered.
          type: boolean
        jid:
          type: string
        phone:
          type: string
        registered:
          type: boolean
        verified_name:
          type: string
      required:
      - phone
      - registered
      - is_on_whatsapp
      - is_business
      type: object
    ContactInfo:
      properties:
        about:
          type: string
        business_address:
          type: string
        business_email:
          type: string
        business_name:
          type: string
        first_name:
          type: string
        full_name:
          type: string
        is_business:
          type: boolean
        jid:
          type: string
        name:
          type: string
        phone:
          type: string
        redacted_phone:
          type: string
        registered:
          type: boolean
        verified_name:
          type: string
      required:
      - jid
      - phone
      type: object
    ContactList:
      items:
        "$ref": "#/components/schemas/ContactInfo"
      type: array
    ContactsCheckRequest:
      properties:
        phones:
          items:
            type: string
          type: array
      required:
      - phones
      type: object
    ContactsCheckResponse:
      items:
        "$ref": "#/components/schemas/ContactCheckEntry"
      type: array
    DebugStats:
      properties:
        gc_cycles:
          type: integer
        goroutines:
          type: integer
        heap_alloc_mb:
          format: int64
          type: integer
        heap_objects:
          format: int64
          type: integer
        heap_sys_mb:
          format: int64
          type: integer
        media_cache_size:
          type: integer
        next_gc_mb:
          format: int64
          type: integer
        total_alloc_mb:
          format: int64
          type: integer
      required:
      - goroutines
      - media_cache_size
      - heap_alloc_mb
      - heap_sys_mb
      - heap_objects
      - total_alloc_mb
      - gc_cycles
      - next_gc_mb
      type: object
    DeliveryLog:
      properties:
        attempt:
          description: 1-based delivery attempt number. 0 when the event was dropped
            before any attempt (delivery queue full).
          type: integer
        error:
          description: Error message on network failure.
          type: string
        event:
          description: Webhook event name (e.g. message).
          type: string
        event_id:
          description: Unique event id, stable across retries of the same event. Also
            sent to your endpoint as the X-Webhook-Event-Id header.
          type: string
        latency_ms:
          description: Round-trip latency in milliseconds.
          type: integer
        status_code:
          description: HTTP status code returned by the webhook endpoint. 0 on network
            error.
          type: integer
        success:
          description: True when status_code is 2xx.
          type: boolean
        timestamp:
          description: RFC3339 UTC time of the delivery attempt.
          type: string
      required:
      - timestamp
      - event_id
      - event
      - attempt
      - status_code
      - latency_ms
      - success
      type: object
    DeliveryLogs:
      items:
        "$ref": "#/components/schemas/DeliveryLog"
      type: array
    DisconnectedWebhook:
      properties:
        detail:
          description: Underlying protocol reason string, for diagnostics. Present
            only with reason=logged_out.
          type: string
        event:
          enum:
          - disconnected
          examples:
          - disconnected
          type: string
        reason:
          description: 'Present when the disconnect is terminal and the bridge will
            NOT reconnect on its own: logged_out (device was unpaired from the phone
            or server side) or stream_replaced (another client took over the session).
            Absent for transient disconnects, which reconnect automatically.'
          enum:
          - logged_out
          - stream_replaced
          type: string
      required:
      - event
      type: object
    EditRequest:
      properties:
        body:
          type: string
        chat:
          type: string
      required:
      - chat
      - body
      type: object
    Error:
      properties:
        code:
          description: Stable machine-readable error code.
          type: string
        error:
          type: string
      required:
      - error
      type: object
    EventDetails:
      properties:
        description:
          type: string
        end_time:
          format: int64
          type: integer
        extra_guests_allowed:
          type: boolean
        has_reminder:
          type: boolean
        is_canceled:
          type: boolean
        is_schedule_call:
          description: True when the event is a scheduled WA voice/video call.
          type: boolean
        join_link:
          type: string
        location:
          "$ref": "#/components/schemas/EventLocation"
        name:
          type: string
        reminder_offset_sec:
          format: int64
          type: integer
        start_time:
          description: Unix epoch seconds.
          format: int64
          type: integer
      required:
      - name
      - description
      - start_time
      type: object
    EventLocation:
      properties:
        address:
          type: string
        lat:
          type: number
        lng:
          type: number
        name:
          type: string
      type: object
    EventResponseWebhook:
      properties:
        chat_id:
          type: string
        contact_id:
          type: string
        event:
          enum:
          - event_response
          examples:
          - event_response
          type: string
        event_id:
          description: message_id of the original EventMessage being RSVP'd to.
          type: string
        extra_guest_count:
          type: integer
        from_me:
          type: boolean
        message_id:
          description: message_id of the response itself.
          type: string
        name:
          type: string
        offline:
          description: Present and true when delivered as part of post-reconnect offline
            catch-up.
          type: boolean
        phone:
          type: string
        response:
          enum:
          - going
          - not_going
          - maybe
          - unknown
          type: string
        response_time_ms:
          description: Sender-supplied response timestamp in milliseconds.
          format: int64
          type: integer
        timestamp:
          format: int64
          type: integer
      required:
      - event
      - contact_id
      - phone
      - chat_id
      - event_id
      - message_id
      - name
      - from_me
      - timestamp
      - response
      - response_time_ms
      - extra_guest_count
      type: object
    HealthResponse:
      properties:
        ok:
          examples:
          - 'true'
          type: boolean
      required:
      - ok
      type: object
    MarkReadRequest:
      properties:
        chat:
          type: string
        sender:
          description: Original sender's JID for group messages.
          type: string
      required:
      - chat
      type: object
    MessageSent:
      properties:
        message_id:
          examples:
          - ACE41E988162AB19A4A7BB7A9E663693
          type: string
        timestamp:
          description: Unix epoch seconds.
          examples:
          - '1777180605'
          format: int64
          type: integer
      required:
      - message_id
      - timestamp
      type: object
    MessageWebhook:
      properties:
        body:
          type: string
        caption:
          description: Caption for image/video/document.
          type: string
        chat_id:
          type: string
        contact_id:
          type: string
        edit:
          description: Present only when the message is an edit/revoke/pin.
          enum:
          - edit
          - revoke
          - pin
          type: string
        event:
          enum:
          - message
          examples:
          - message
          type: string
        event_details:
          "$ref": "#/components/schemas/EventDetails"
          description: Calendar/scheduled-event payload. Present only when type=event.
        forwarded:
          description: Present and true only when forwarded.
          type: boolean
        from_me:
          type: boolean
        is_broadcast:
          description: Present and true only when sent via a broadcast list.
          type: boolean
        is_group:
          type: boolean
        max_answers:
          description: Number of choices a voter may pick. 1 = single choice. Present
            only when type=poll.
          type: integer
        media_type:
          enum:
          - ''
          - image
          - video
          - audio
          - voice
          - document
          - sticker
          type: string
        mentions:
          description: Canonicalized mentioned contact JIDs.
          items:
            type: string
          type: array
        message_id:
          type: string
        name:
          description: Push name from the sender's device.
          type: string
        offline:
          description: Present and true when delivered as part of post-reconnect offline
            catch-up. Bracketed by offline_sync_preview / offline_sync_completed webhooks.
          type: boolean
        options:
          description: Poll choices, in display order. Present only when type=poll.
          items:
            type: string
          type: array
        phone:
          description: Digits-only phone number, or empty for LID-only senders.
          type: string
        quoted:
          "$ref": "#/components/schemas/QuotedInfo"
        reaction:
          "$ref": "#/components/schemas/ReactionInfo"
        timestamp:
          format: int64
          type: integer
        type:
          enum:
          - text
          - media
          - reaction
          - poll
          - location
          - event
          type: string
        verified_business:
          description: Present and true only when the sender is a verified business.
          type: boolean
      required:
      - event
      - message_id
      - contact_id
      - phone
      - chat_id
      - name
      - body
      - type
      - media_type
      - is_group
      - from_me
      - timestamp
      type: object
    OfflineSyncCompletedWebhook:
      properties:
        count:
          type: integer
        event:
          enum:
          - offline_sync_completed
          examples:
          - offline_sync_completed
          type: string
      required:
      - event
      - count
      type: object
    OfflineSyncPreviewWebhook:
      properties:
        app_data_changes:
          type: integer
        event:
          enum:
          - offline_sync_preview
          examples:
          - offline_sync_preview
          type: string
        messages:
          type: integer
        notifications:
          type: integer
        receipts:
          type: integer
        total:
          type: integer
      required:
      - event
      - total
      - messages
      - notifications
      - receipts
      - app_data_changes
      type: object
    OkResponse:
      properties:
        ok:
          examples:
          - 'true'
          type: boolean
      required:
      - ok
      type: object
    PairRequest:
      properties:
        phone:
          description: Phone number (digits, optional + prefix).
          examples:
          - '15550001234'
          type: string
      required:
      - phone
      type: object
    PairResponse:
      properties:
        code:
          description: 8-character link code (with hyphen) to enter on the phone.
          examples:
          - ABCD-EFGH
          type: string
      required:
      - code
      type: object
    PollVoteWebhook:
      properties:
        chat_id:
          type: string
        contact_id:
          type: string
        event:
          enum:
          - poll_vote
          examples:
          - poll_vote
          type: string
        from_me:
          type: boolean
        message_id:
          description: message_id of the vote update itself.
          type: string
        name:
          type: string
        offline:
          description: Present and true when delivered as part of post-reconnect offline
            catch-up.
          type: boolean
        phone:
          type: string
        poll_id:
          description: message_id of the original poll being voted on.
          type: string
        selected_options:
          description: Chosen option names. Empty array clears the vote, or means
            the original poll was not cached.
          items:
            type: string
          type: array
        timestamp:
          format: int64
          type: integer
      required:
      - event
      - contact_id
      - phone
      - chat_id
      - poll_id
      - message_id
      - name
      - from_me
      - timestamp
      - selected_options
      type: object
    PresenceRequest:
      properties:
        state:
          enum:
          - available
          - unavailable
          type: string
      required:
      - state
      type: object
    PresenceSubscribeRequest:
      properties:
        chat:
          description: Contact JID to subscribe presence for.
          type: string
      required:
      - chat
      type: object
    ProfileAvatarRequest:
      properties:
        url:
          description: HTTP(S) URL to a JPEG image. Server fetches it and uploads
            as the profile picture.
          format: uri
          type: string
      required:
      - url
      type: object
    ProfileAvatarResponse:
      properties:
        picture_id:
          description: WhatsApp ID assigned to the new picture.
          type: string
      required:
      - picture_id
      type: object
    ProfileInfo:
      properties:
        about:
          type: string
        jid:
          type: string
        name:
          type: string
        phone:
          type: string
      required:
      - jid
      - phone
      - name
      type: object
    ProfilePictureUpdatedWebhook:
      properties:
        event:
          enum:
          - profile_picture_updated
          examples:
          - profile_picture_updated
          type: string
        picture_id:
          description: New picture ID assigned by WhatsApp. Absent when remove is
            true.
          type: string
        remove:
          description: True when the profile picture was removed.
          type: boolean
        timestamp:
          format: int64
          type: integer
      required:
      - event
      - remove
      - timestamp
      type: object
    ProfileQRResponse:
      properties:
        link:
          examples:
          - https://wa.me/qr/ABCDEFGHIJKL
          format: uri
          type: string
      required:
      - link
      type: object
    ProfileStatusRequest:
      properties:
        status:
          type: string
      required:
      - status
      type: object
    QuotedInfo:
      properties:
        body:
          type: string
        contact_id:
          type: string
        message_id:
          type: string
      required:
      - message_id
      - contact_id
      - body
      type: object
    ReactionInfo:
      properties:
        emoji:
          examples:
          - "\U0001F44D"
          type: string
        target_contact:
          type: string
        target_from_me:
          type: boolean
        target_id:
          type: string
      required:
      - emoji
      - target_id
      - target_contact
      - target_from_me
      type: object
    SendEventLocation:
      properties:
        address:
          type: string
        lat:
          type: number
        lng:
          type: number
        name:
          type: string
      type: object
    SendEventRequest:
      properties:
        chat:
          description: Phone or full JID.
          type: string
        description:
          type: string
        end_time:
          format: int64
          type: integer
        extra_guests_allowed:
          type: boolean
        has_reminder:
          type: boolean
        is_schedule_call:
          description: True for a scheduled WA voice/video call.
          type: boolean
        join_link:
          type: string
        location:
          "$ref": "#/components/schemas/SendEventLocation"
        name:
          description: Event title. Surfaced as body on the resulting message webhook.
          type: string
        reminder_offset_sec:
          format: int64
          type: integer
        start_time:
          description: Unix epoch seconds.
          format: int64
          type: integer
      required:
      - chat
      - name
      - start_time
      type: object
    SendLocationRequest:
      properties:
        address:
          type: string
        chat:
          type: string
        lat:
          type: number
        lng:
          type: number
        name:
          type: string
      required:
      - chat
      - lat
      - lng
      type: object
    SendMediaRequest:
      properties:
        caption:
          type: string
        chat:
          description: Phone or full JID.
          type: string
        url:
          description: HTTP(S) URL to fetch. Media type inferred from response Content-Type.
          format: uri
          type: string
      required:
      - chat
      - url
      type: object
    SendPollRequest:
      properties:
        chat:
          type: string
        max_answers:
          description: 1 = single choice. Higher = multiple choice.
          minimum: 1
          type: integer
        options:
          items:
            type: string
          maxItems: 12
          minItems: 2
          type: array
        question:
          type: string
      required:
      - chat
      - question
      - options
      type: object
    SendReactionRequest:
      properties:
        chat:
          description: Chat to send the reaction in (DM peer or group JID).
          type: string
        emoji:
          description: Empty string removes the reaction.
          type: string
        from_me:
          description: Set true when reacting to a message you sent. Ignored if 'sender'
            is provided.
          type: boolean
        message_id:
          type: string
        sender:
          description: For group reactions, the original sender's JID. Optional in
            DMs.
          type: string
      required:
      - chat
      - message_id
      type: object
    SendTextRequest:
      properties:
        body:
          type: string
        chat:
          description: Phone or full JID.
          type: string
      required:
      - chat
      - body
      type: object
    SendTypingRequest:
      properties:
        chat:
          type: string
        mode:
          enum:
          - text
          - voice
          type: string
        state:
          enum:
          - composing
          - paused
          type: string
      required:
      - chat
      type: object
    StatusPrivacyEntry:
      properties:
        is_default:
          type: boolean
        list:
          items:
            type: string
          type: array
        type:
          enum:
          - contacts
          - contact_blacklist
          - whitelist
          type: string
      required:
      - type
      - list
      - is_default
      type: object
    StatusPrivacyResponse:
      items:
        "$ref": "#/components/schemas/StatusPrivacyEntry"
      type: array
    StatusResponse:
      properties:
        jid:
          description: Full WhatsApp JID (e.g. 15550001234@s.whatsapp.net), present
            when connected.
          type: string
        name:
          description: WhatsApp push name, present when connected.
          type: string
        phone:
          description: Digits-only phone number, present when connected.
          type: string
        status:
          enum:
          - unpaired
          - connected
          - disconnected
          type: string
      required:
      - status
      type: object
    TypingWebhook:
      properties:
        chat_id:
          type: string
        contact_id:
          type: string
        event:
          enum:
          - typing
          examples:
          - typing
          type: string
        mode:
          enum:
          - text
          - voice
          type: string
        state:
          enum:
          - composing
          - paused
          type: string
      required:
      - event
      - contact_id
      - chat_id
      - state
      - mode
      type: object
    InstanceSummary:
      properties:
        id:
          type: string
          description: Bridge id (UUID).
        customer_ref:
          type: string
          description: The reference you chose at provisioning.
        state:
          type: string
          enum:
          - provisioning
          - running
          - paused
          - suspended
        created_at:
          type: integer
          description: Unix timestamp.
      required:
      - id
      - customer_ref
      - state
      type: object
    InstanceCreated:
      allOf:
      - "$ref": "#/components/schemas/InstanceSummary"
      - type: object
        properties:
          webhook_secret:
            type: string
            description: 'Secret used to authenticate webhooks to your endpoint: sent
              as `Authorization: Bearer <webhook_secret>` and as the key of the `X-Webhook-Signature`
              HMAC. Verify before trusting payloads. Null while the bridge is still
              provisioning.'
  securitySchemes:
    bearerAuth:
      description: Account API key (`sk_...`), created from the WABridges dashboard.
      scheme: bearer
      type: http
  parameters:
    CustomerRef:
      description: The customer_ref you chose when provisioning the bridge.
      in: path
      name: customer_ref
      required: true
      schema:
        type: string
info:
  description: |
    REST API for WABridges hosted WhatsApp bridges. Authenticate every request
    with your account API key: `Authorization: Bearer sk_...` (create one from
    the dashboard). Bridge endpoints are addressed by the `customer_ref` you
    chose at provisioning: `https://wabridges.com/api/instances/{customer_ref}/proxy/...`.

    Outbound webhooks to your `webhook_url` are delivered in order with
    at-least-once semantics: failed deliveries (network error, 429, or 5xx)
    are retried up to 5 times with backoff (10s / 30s / 1m / 3m); other 4xx
    responses are not retried. Answer 2xx quickly and do real work
    asynchronously. Every delivery carries an `X-Webhook-Event-Id` header
    that is stable across retries - deduplicate on it - and an
    `X-Webhook-Attempt` header (1-based).

    Each delivery is authenticated two ways with your bridge's
    `webhook_secret` (returned at provisioning, shown on the bridge page):
    the legacy `Authorization: Bearer <webhook_secret>` header, and an HMAC
    signature `X-Webhook-Signature: t=<unix>,v1=<hex>` where `v1` is
    HMAC-SHA256 over `<t>.<raw body>` keyed with the secret. Prefer
    verifying the signature; reject stale `t` (older than ~5 minutes).

    Every HTTP response carries an `X-Request-Id` header identifying the
    request. A well-formed inbound `X-Request-Id` (1–64 characters of
    `[A-Za-z0-9_-]`) is echoed back; anything else is replaced with a
    generated id. 503 responses (bridge not connected) include a
    `Retry-After` header with the number of seconds to wait before
    retrying.
  title: WABridges API
  version: 1.0.0
openapi: 3.1.0
paths:
  "/contacts":
    get:
      description: |-
        Returns a plain JSON array of contacts sorted by JID ascending.
        Without parameters the full list is returned (back-compat). To page,
        pass `limit` and use the last JID of the previous page as `after`
        on the next request; a page shorter than `limit` means you have
        reached the end.
      operationId: listContacts
      parameters:
      - description: Maximum number of contacts to return. Values above 1000 are clamped
          to 1000. 0, absent, or negative returns everything (unlimited).
        in: query
        name: limit
        required: false
        schema:
          type: integer
      - description: 'JID cursor: only contacts with a JID strictly greater than this
          value (in ascending JID order) are returned. Pass the last JID of the previous
          page to fetch the next one.'
        in: query
        name: after
        required: false
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ContactList"
          description: contact list
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
      summary: List all contacts the bridge knows about
      tags:
      - contacts
  "/contacts/{jid}":
    get:
      operationId: contactInfo
      parameters:
      - description: Full JID or phone (e.g. 15550001234, 15550001234@s.whatsapp.net).
        in: path
        name: jid
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ContactInfo"
          description: contact info
        '400':
          "$ref": "#/components/responses/BadRequest"
        '404':
          "$ref": "#/components/responses/NotFound"
        '500':
          "$ref": "#/components/responses/InternalError"
      summary: Get info for a single contact
      tags:
      - contacts
  "/contacts/{jid}/avatar":
    get:
      operationId: contactAvatar
      parameters:
      - description: Full JID or phone.
        in: path
        name: jid
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            image/jpeg:
              schema:
                format: binary
                type: string
          description: avatar bytes
        '404':
          "$ref": "#/components/responses/NotFound"
        '500':
          "$ref": "#/components/responses/InternalError"
      summary: Download a contact's avatar
      tags:
      - contacts
  "/contacts/check":
    post:
      operationId: contactsCheck
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  phones:
                  - '15550001234'
                  - '00000000000'
            schema:
              "$ref": "#/components/schemas/ContactsCheckRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ContactsCheckResponse"
          description: check result
        '400':
          "$ref": "#/components/responses/BadRequest"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
      summary: Check which phone numbers are on WhatsApp
      tags:
      - contacts
  "/debug/stats":
    get:
      operationId: debugStats
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/DebugStats"
          description: stats
      summary: Runtime stats (goroutines, heap, cache)
      tags:
      - debug
  "/healthz":
    get:
      description: Used by the container HEALTHCHECK. Always returns 200 while the
        process is up. Does NOT check WhatsApp connection — use /status for that.
      operationId: healthz
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/HealthResponse"
          description: process is up
      summary: Process liveness probe (unauthenticated)
      tags:
      - session
  "/logout":
    delete:
      operationId: logout
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OkResponse"
          description: logged out
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Log out of WhatsApp session
      tags:
      - session
  "/messages/{id}":
    delete:
      operationId: revokeMessage
      parameters:
      - description: A message_id returned from a send call or seen on a webhook.
        in: path
        name: id
        required: true
        schema:
          type: string
      - description: JID or phone identifying the chat the message was sent to.
        in: query
        name: chat
        required: true
        schema:
          type: string
      - description: 'Optional key (max 255 chars) making this request safe to retry.
          First use performs the send and stores the response for 24h; a repeat of
          the same key replays the stored response (marked with an X-Idempotency-Replayed:
          true header) without sending anything. A concurrent duplicate returns 409
          idempotency_conflict. Only successful responses consume a key. Use a unique
          value per logical send (e.g. a UUID or order id).'
        in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageSent"
          description: revoked
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Revoke a sent message for everyone
      tags:
      - messaging
  "/messages/{id}/edit":
    patch:
      operationId: editMessage
      parameters:
      - description: A message_id returned from a send call or seen on a webhook.
        in: path
        name: id
        required: true
        schema:
          type: string
      - description: 'Optional key (max 255 chars) making this request safe to retry.
          First use performs the send and stores the response for 24h; a repeat of
          the same key replays the stored response (marked with an X-Idempotency-Replayed:
          true header) without sending anything. A concurrent duplicate returns 409
          idempotency_conflict. Only successful responses consume a key. Use a unique
          value per logical send (e.g. a UUID or order id).'
        in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  body: corrected text
                  chat: 15550001234@s.whatsapp.net
            schema:
              "$ref": "#/components/schemas/EditRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageSent"
          description: edit sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Edit a sent text message
      tags:
      - messaging
  "/messages/{id}/media":
    get:
      operationId: downloadMedia
      parameters:
      - description: A message_id returned from a send call or seen on a webhook.
        in: path
        name: id
        required: true
        schema:
          type: string
      responses:
        '200':
          content:
            application/octet-stream:
              schema:
                format: binary
                type: string
            audio/ogg:
              schema:
                format: binary
                type: string
            image/jpeg:
              schema:
                format: binary
                type: string
            video/mp4:
              schema:
                format: binary
                type: string
          description: raw media bytes
        '404':
          "$ref": "#/components/responses/NotFound"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
      summary: Download received media by message_id (cached 3h)
      tags:
      - media
  "/messages/{id}/read":
    post:
      operationId: markRead
      parameters:
      - description: A message_id returned from a send call or seen on a webhook.
        in: path
        name: id
        required: true
        schema:
          type: string
      requestBody:
        content:
          application/json:
            examples:
              dm:
                value:
                  chat: 15550001234@s.whatsapp.net
              group:
                value:
                  chat: 120363000000000001@g.us
                  sender: 15550001234@s.whatsapp.net
            schema:
              "$ref": "#/components/schemas/MarkReadRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OkResponse"
          description: receipt sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Send a read receipt
      tags:
      - messaging
  "/pair":
    post:
      operationId: pairPhone
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  phone: '15550001234'
            schema:
              "$ref": "#/components/schemas/PairRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/PairResponse"
          description: pairing code to enter on the phone
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
      summary: Pair via link code sent to phone
      tags:
      - session
  "/presence":
    post:
      operationId: setPresence
      requestBody:
        content:
          application/json:
            examples:
              offline:
                value:
                  state: unavailable
              online:
                value:
                  state: available
            schema:
              "$ref": "#/components/schemas/PresenceRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OkResponse"
          description: presence updated
        '400':
          "$ref": "#/components/responses/BadRequest"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Set your online/offline presence
      tags:
      - presence
  "/presence/subscribe":
    post:
      description: WhatsApp only pushes ChatPresence (typing/recording) and Presence
        (online/offline) for contacts the client has subscribed to. Subscriptions
        decay after a few minutes; refresh periodically. Requires global presence
        to be `available` first.
      operationId: subscribePresence
      requestBody:
        content:
          application/json:
            examples:
              chat:
                value:
                  chat: '15550001234'
            schema:
              "$ref": "#/components/schemas/PresenceSubscribeRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OkResponse"
          description: subscribed
        '400':
          "$ref": "#/components/responses/BadRequest"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Subscribe to a contact's presence so typing/online events are delivered
      tags:
      - presence
  "/profile":
    get:
      operationId: getProfile
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ProfileInfo"
          description: profile info
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Your own profile
      tags:
      - profile
  "/profile/avatar":
    delete:
      operationId: deleteProfileAvatar
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OkResponse"
          description: avatar removed
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Remove your profile picture
      tags:
      - profile
    get:
      operationId: profileAvatar
      responses:
        '200':
          content:
            image/jpeg:
              schema:
                format: binary
                type: string
          description: avatar bytes
        '404':
          "$ref": "#/components/responses/NotFound"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Download your own avatar
      tags:
      - profile
    put:
      description: Server fetches the URL (must return JPEG) and uploads it as the
        account profile picture.
      operationId: setProfileAvatar
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  url: https://example.com/me.jpg
            schema:
              "$ref": "#/components/schemas/ProfileAvatarRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ProfileAvatarResponse"
          description: avatar updated
        '400':
          "$ref": "#/components/responses/BadRequest"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Set your profile picture from a URL
      tags:
      - profile
  "/profile/qr":
    get:
      operationId: profileQR
      parameters:
      - description: When true, revoke the current code and issue a new one.
        in: query
        name: revoke
        required: false
        schema:
          type: boolean
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ProfileQRResponse"
          description: shareable wa.me/qr URL
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
      summary: Personal QR link to share your contact
      tags:
      - profile
  "/profile/status":
    patch:
      operationId: setProfileStatus
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  status: Hey there!
            schema:
              "$ref": "#/components/schemas/ProfileStatusRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OkResponse"
          description: status updated
        '400':
          "$ref": "#/components/responses/BadRequest"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Set your About status text
      tags:
      - profile
  "/qr":
    get:
      operationId: getQR
      responses:
        '200':
          content:
            image/png:
              schema:
                format: binary
                type: string
          description: QR code image
        '409':
          "$ref": "#/components/responses/Conflict"
        '504':
          "$ref": "#/components/responses/InternalError"
      summary: QR code PNG for pairing
      tags:
      - session
  "/send/event":
    post:
      description: |-
        Sends a WA EventMessage. The bridge embeds a fresh MessageSecret in
        MessageContextInfo so future EncEventResponseMessage RSVPs can be
        decrypted and delivered as event_response webhooks.
      operationId: sendEvent
      parameters:
      - description: 'Optional key (max 255 chars) making this request safe to retry.
          First use performs the send and stores the response for 24h; a repeat of
          the same key replays the stored response (marked with an X-Idempotency-Replayed:
          true header) without sending anything. A concurrent duplicate returns 409
          idempotency_conflict. Only successful responses consume a key. Use a unique
          value per logical send (e.g. a UUID or order id).'
        in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            examples:
              call:
                value:
                  chat: 120363000000000001@g.us
                  description: weekly status
                  end_time: 1777338000
                  has_reminder: true
                  is_schedule_call: true
                  join_link: https://meet.example.com/team-sync
                  name: Team sync
                  reminder_offset_sec: 900
                  start_time: 1777334400
            schema:
              "$ref": "#/components/schemas/SendEventRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageSent"
          description: event sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Send a calendar event
      tags:
      - messaging
  "/send/location":
    post:
      operationId: sendLocation
      parameters:
      - description: 'Optional key (max 255 chars) making this request safe to retry.
          First use performs the send and stores the response for 24h; a repeat of
          the same key replays the stored response (marked with an X-Idempotency-Replayed:
          true header) without sending anything. A concurrent duplicate returns 409
          idempotency_conflict. Only successful responses consume a key. Use a unique
          value per logical send (e.g. a UUID or order id).'
        in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  address: Plaza de la Constitución, Mexico City
                  chat: '15550001234'
                  lat: 19.432608
                  lng: -99.133209
                  name: Zócalo
            schema:
              "$ref": "#/components/schemas/SendLocationRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageSent"
          description: message sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Send a location message
      tags:
      - messaging
  "/send/media":
    post:
      description: Media type is inferred from the Content-Type of the URL response.
      operationId: sendMedia
      parameters:
      - description: 'Optional key (max 255 chars) making this request safe to retry.
          First use performs the send and stores the response for 24h; a repeat of
          the same key replays the stored response (marked with an X-Idempotency-Replayed:
          true header) without sending anything. A concurrent duplicate returns 409
          idempotency_conflict. Only successful responses consume a key. Use a unique
          value per logical send (e.g. a UUID or order id).'
        in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            examples:
              audio:
                value:
                  chat: '15550001234'
                  url: https://example.com/voice.ogg
              document:
                value:
                  caption: Q1 report
                  chat: '15550001234'
                  url: https://example.com/report.pdf
              image:
                value:
                  caption: check this out
                  chat: '15550001234'
                  url: https://example.com/photo.jpg
              video:
                value:
                  caption: watch this
                  chat: '15550001234'
                  url: https://example.com/clip.mp4
            schema:
              "$ref": "#/components/schemas/SendMediaRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageSent"
          description: message sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Send a media message (image, video, audio, document)
      tags:
      - messaging
  "/send/poll":
    post:
      operationId: sendPoll
      parameters:
      - description: 'Optional key (max 255 chars) making this request safe to retry.
          First use performs the send and stores the response for 24h; a repeat of
          the same key replays the stored response (marked with an X-Idempotency-Replayed:
          true header) without sending anything. A concurrent duplicate returns 409
          idempotency_conflict. Only successful responses consume a key. Use a unique
          value per logical send (e.g. a UUID or order id).'
        in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            examples:
              multi:
                value:
                  chat: '15550001234'
                  max_answers: 3
                  options:
                  - Mon
                  - Tue
                  - Wed
                  - Thu
                  - Fri
                  question: Which days work for you?
              single:
                value:
                  chat: '15550001234'
                  max_answers: 1
                  options:
                  - Red
                  - Green
                  - Blue
                  question: Favorite color?
            schema:
              "$ref": "#/components/schemas/SendPollRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageSent"
          description: poll sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Send a poll
      tags:
      - messaging
  "/send/reaction":
    post:
      operationId: sendReaction
      parameters:
      - description: 'Optional key (max 255 chars) making this request safe to retry.
          First use performs the send and stores the response for 24h; a repeat of
          the same key replays the stored response (marked with an X-Idempotency-Replayed:
          true header) without sending anything. A concurrent duplicate returns 409
          idempotency_conflict. Only successful responses consume a key. Use a unique
          value per logical send (e.g. a UUID or order id).'
        in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            examples:
              dm:
                value:
                  chat: '15550001234'
                  emoji: "\U0001F44D"
                  message_id: ACE41E988162AB19A4A7BB7A9E663693
              group:
                value:
                  chat: 120363000000000001@g.us
                  emoji: "❤️"
                  message_id: ACE41E988162AB19A4A7BB7A9E663693
                  sender: 15550001234@s.whatsapp.net
              remove:
                value:
                  chat: '15550001234'
                  emoji: ''
                  message_id: ACE41E988162AB19A4A7BB7A9E663693
            schema:
              "$ref": "#/components/schemas/SendReactionRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageSent"
          description: reaction sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: React to a message with an emoji
      tags:
      - messaging
  "/send/text":
    post:
      operationId: sendText
      parameters:
      - description: 'Optional key (max 255 chars) making this request safe to retry.
          First use performs the send and stores the response for 24h; a repeat of
          the same key replays the stored response (marked with an X-Idempotency-Replayed:
          true header) without sending anything. A concurrent duplicate returns 409
          idempotency_conflict. Only successful responses consume a key. Use a unique
          value per logical send (e.g. a UUID or order id).'
        in: header
        name: Idempotency-Key
        required: false
        schema:
          type: string
      requestBody:
        content:
          application/json:
            examples:
              full_jid:
                value:
                  body: hello
                  chat: 15550001234@s.whatsapp.net
              group:
                value:
                  body: hi team
                  chat: 120363000000000001@g.us
              plain_number:
                value:
                  body: hello
                  chat: '15550001234'
            schema:
              "$ref": "#/components/schemas/SendTextRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/MessageSent"
          description: message sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '409':
          "$ref": "#/components/responses/Conflict"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Send a text message
      tags:
      - messaging
  "/send/typing":
    post:
      operationId: sendTyping
      requestBody:
        content:
          application/json:
            examples:
              composing:
                value:
                  chat: '15550001234'
                  state: composing
              paused:
                value:
                  chat: '15550001234'
                  state: paused
              recording_voice:
                value:
                  chat: '15550001234'
                  mode: voice
                  state: composing
            schema:
              "$ref": "#/components/schemas/SendTypingRequest"
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/OkResponse"
          description: indicator sent
        '400':
          "$ref": "#/components/responses/BadRequest"
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Send a typing indicator
      tags:
      - presence
  "/status":
    get:
      operationId: getStatus
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StatusResponse"
          description: current connection state
      summary: Connection status
      tags:
      - session
  "/status/privacy":
    get:
      operationId: getStatusPrivacy
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/StatusPrivacyResponse"
          description: privacy lists
        '500':
          "$ref": "#/components/responses/InternalError"
        '502':
          "$ref": "#/components/responses/UpstreamError"
        '503':
          "$ref": "#/components/responses/ServiceUnavailable"
      summary: Get who can see your status updates
      tags:
      - privacy
  "/webhook-logs":
    get:
      description: Returns up to 200 most recent outbound webhook delivery attempts,
        newest first. Ring buffer — oldest entries are evicted once full.
      operationId: getWebhookLogs
      responses:
        '200':
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/DeliveryLogs"
          description: delivery log entries
      summary: Recent webhook delivery attempts
      tags:
      - session
  "/api/instances":
    servers:
    - url: https://wabridges.com
    post:
      operationId: createInstance
      summary: Provision a bridge
      description: Creates and starts a bridge. Idempotent on customer_ref - posting
        an existing ref returns the existing bridge instead of creating a duplicate.
        Requires an active subscription; trial bridges are created from the dashboard.
      tags:
      - instances
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - customer_ref
              - webhook_url
              properties:
                customer_ref:
                  type: string
                  description: Any string you control (user id, order number). Addresses
                    the bridge in all later calls.
                webhook_url:
                  type: string
                  description: HTTPS endpoint that receives inbound events.
            examples:
              default:
                value:
                  customer_ref: user-123
                  webhook_url: https://your-app.com/whatsapp/hook
      responses:
        '201':
          description: bridge created
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/InstanceCreated"
        '200':
          description: a bridge already existed for this customer_ref; returned unchanged
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/InstanceCreated"
        '400':
          "$ref": "#/components/responses/BadRequest"
        '402':
          description: No active subscription, or account payment past due
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Account bridge limit reached (default 10 - contact support
            to raise)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    get:
      operationId: listInstances
      summary: List bridges
      tags:
      - instances
      responses:
        '200':
          description: all bridges on the account, newest first
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/InstanceSummary"
  "/api/instances/{customer_ref}":
    servers:
    - url: https://wabridges.com
    get:
      operationId: getInstance
      summary: Fetch one bridge with live manager state
      tags:
      - instances
      parameters:
      - "$ref": "#/components/parameters/CustomerRef"
      responses:
        '200':
          description: bridge record plus live container state under `manager`
          content:
            application/json:
              schema:
                allOf:
                - "$ref": "#/components/schemas/InstanceSummary"
                - type: object
                  properties:
                    webhook_url:
                      type: string
                    webhook_secret:
                      type: string
                      description: Secret used to sign outbound webhooks to your endpoint.
                    on_trial:
                      type: boolean
                    manager:
                      type: object
                      description: Live state from the orchestrator.
        '404':
          "$ref": "#/components/responses/NotFound"
  "/api/instances/{customer_ref}/logs":
    servers:
    - url: https://wabridges.com
    get:
      operationId: streamInstanceLogs
      summary: Stream bridge logs (SSE)
      tags:
      - instances
      parameters:
      - "$ref": "#/components/parameters/CustomerRef"
      - in: query
        name: tail
        required: false
        schema:
          type: integer
          default: 100
          minimum: 1
          maximum: 10000
        description: Number of trailing log lines to start from.
      responses:
        '200':
          description: server-sent event stream of log lines
          content:
            text/event-stream:
              schema:
                type: string
        '404':
          "$ref": "#/components/responses/NotFound"
security:
- bearerAuth: []
servers:
- url: https://wabridges.com/api/instances/{customer_ref}/proxy
  description: Per-bridge proxy - all bridge endpoints below are relative to this
    base
  variables:
    customer_ref:
      default: user-123
      description: The customer_ref you chose when provisioning the bridge
tags:
- description: Pairing, status, QR
  name: session
- description: Send and manage messages
  name: messaging
- description: Inbound media downloads
  name: media
- description: Contact info and discovery
  name: contacts
- description: Your own profile
  name: profile
- description: Typing and online state
  name: presence
- description: Privacy settings
  name: privacy
- description: Diagnostic endpoints
  name: debug
- description: Provision and manage bridge instances
  name: instances
webhooks:
  call_incoming:
    post:
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  call_id: ABCDEF123456
                  contact_id: 15550001234@s.whatsapp.net
                  event: call_incoming
                  platform: android
                  timestamp: 1777180605
            schema:
              "$ref": "#/components/schemas/CallIncomingWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Incoming call
  call_terminated:
    post:
      description: "`reason` is `timeout` when missed, `hangup` when the caller hung
        up."
      requestBody:
        content:
          application/json:
            examples:
              hangup:
                value:
                  call_id: ABCDEF123456
                  contact_id: 15550001234@s.whatsapp.net
                  event: call_terminated
                  reason: hangup
                  timestamp: 1777180720
              missed:
                value:
                  call_id: ABCDEF123456
                  contact_id: 15550001234@s.whatsapp.net
                  event: call_terminated
                  reason: timeout
                  timestamp: 1777180700
            schema:
              "$ref": "#/components/schemas/CallTerminatedWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Call ended (missed or hung up)
  connected:
    post:
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  event: connected
                  phone: '15550001234'
            schema:
              "$ref": "#/components/schemas/ConnectedWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: WhatsApp session connected
  disconnected:
    post:
      description: |-
        Transient disconnects carry no reason and reconnect automatically.
        When reason is present the session is terminal: logged_out means the
        device was unpaired and must be re-paired; stream_replaced means
        another client took over the session.
      requestBody:
        content:
          application/json:
            examples:
              logged_out:
                value:
                  detail: '401: logged out'
                  event: disconnected
                  reason: logged_out
              transient:
                value:
                  event: disconnected
            schema:
              "$ref": "#/components/schemas/DisconnectedWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: WhatsApp session disconnected
  event_response:
    post:
      description: |-
        Fired when a contact responds to an EventMessage with going /
        not_going / maybe. Decrypted from EncEventResponseMessage on
        delivery.
      requestBody:
        content:
          application/json:
            examples:
              going:
                value:
                  chat_id: 15550009999@g.us
                  contact_id: 15550001234@s.whatsapp.net
                  event: event_response
                  event_id: ACEVENT0001
                  extra_guest_count: 1
                  from_me: false
                  message_id: ACEVRESP0001
                  name: John Doe
                  phone: '15550001234'
                  response: going
                  response_time_ms: 1777330700123
                  timestamp: 1777330700
              offline_catchup:
                value:
                  chat_id: 15550009999@g.us
                  contact_id: 15550001234@s.whatsapp.net
                  event: event_response
                  event_id: ACEVENT0001
                  extra_guest_count: 0
                  from_me: false
                  message_id: ACEVRESP0099
                  name: John Doe
                  offline: true
                  phone: '15550001234'
                  response: going
                  response_time_ms: 1777180900123
                  timestamp: 1777180900
            schema:
              "$ref": "#/components/schemas/EventResponseWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Contact RSVP'd to a calendar event
  message:
    post:
      description: |-
        Fired for every WA message the bridge sees, including messages sent
        from the user's own phone (from_me: true). Pure protocol/sync events
        with no body, caption, media, or reaction are filtered out.
      requestBody:
        content:
          application/json:
            examples:
              event_created:
                value:
                  body: Team sync
                  chat_id: 15550009999@g.us
                  contact_id: 15550001234@s.whatsapp.net
                  event: message
                  event_details:
                    description: weekly status
                    end_time: 1777338000
                    join_link: https://meet.example.com/team-sync
                    name: Team sync
                    start_time: 1777334400
                  from_me: false
                  is_group: true
                  media_type: ''
                  message_id: ACEVENT0001
                  name: John Doe
                  phone: '15550001234'
                  timestamp: 1777330600
                  type: event
              image_with_caption:
                value:
                  body: ''
                  caption: look at this
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: message
                  from_me: false
                  is_group: false
                  media_type: image
                  message_id: ACE7253B69494A9CB9D671C943A7F4AB
                  name: John Doe
                  phone: '15550001234'
                  timestamp: 1777180605
                  type: media
              offline_catchup:
                value:
                  body: sent while bridge was down
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: message
                  from_me: false
                  is_group: false
                  media_type: ''
                  message_id: ACOFF0001
                  name: John Doe
                  offline: true
                  phone: '15550001234'
                  timestamp: 1777180605
                  type: text
              poll_created:
                value:
                  body: pick one
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: message
                  from_me: false
                  is_group: false
                  max_answers: 1
                  media_type: ''
                  message_id: ACPOLL0001
                  name: John Doe
                  options:
                  - red
                  - green
                  - blue
                  phone: '15550001234'
                  timestamp: 1777330323
                  type: poll
              reaction_added:
                value:
                  body: ''
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550009999@s.whatsapp.net
                  event: message
                  from_me: false
                  is_group: false
                  media_type: ''
                  message_id: ACREACT0001
                  name: Jane
                  phone: '15550009999'
                  reaction:
                    emoji: "\U0001F44D"
                    target_contact: 15550001234@s.whatsapp.net
                    target_from_me: true
                    target_id: ACE41E988162AB19A4A7BB7A9E663693
                  timestamp: 1777261100
                  type: reaction
              text:
                value:
                  body: hello
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: message
                  from_me: false
                  is_group: false
                  media_type: ''
                  message_id: ACE41E988162AB19A4A7BB7A9E663693
                  name: John Doe
                  phone: '15550001234'
                  timestamp: 1777180605
                  type: text
            schema:
              "$ref": "#/components/schemas/MessageWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Inbound or outbound message
  offline_sync_completed:
    post:
      description: Fired when the WA server has finished replaying missed events.
        Subsequent webhooks no longer carry offline:true.
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  count: 142
                  event: offline_sync_completed
            schema:
              "$ref": "#/components/schemas/OfflineSyncCompletedWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Post-reconnect catch-up has finished
  offline_sync_preview:
    post:
      description: |-
        Fired once after reconnect when the WA server has buffered events to
        replay. Subsequent message / poll_vote / event_response webhooks
        delivered during the catch-up window carry "offline": true. The
        window closes with offline_sync_completed.
      requestBody:
        content:
          application/json:
            examples:
              default:
                value:
                  app_data_changes: 0
                  event: offline_sync_preview
                  messages: 120
                  notifications: 8
                  receipts: 14
                  total: 142
            schema:
              "$ref": "#/components/schemas/OfflineSyncPreviewWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Post-reconnect catch-up is starting
  poll_vote:
    post:
      description: |-
        Fired when a contact votes or changes their vote on a poll.
        selected_options is empty when the vote is cleared or when the
        original poll is older than the 3-hour message cache TTL (the WA
        protocol only delivers SHA-256 hashes of chosen options, which the
        bridge resolves back to names using the cached PollCreationMessage).
      requestBody:
        content:
          application/json:
            examples:
              offline_catchup:
                value:
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: poll_vote
                  from_me: false
                  message_id: ACVOTE0099
                  name: John Doe
                  offline: true
                  phone: '15550001234'
                  poll_id: ACPOLL0001
                  selected_options:
                  - red
                  timestamp: 1777180800
              single_choice:
                value:
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: poll_vote
                  from_me: false
                  message_id: ACVOTE0001
                  name: John Doe
                  phone: '15550001234'
                  poll_id: ACPOLL0001
                  selected_options:
                  - red
                  timestamp: 1777330500
            schema:
              "$ref": "#/components/schemas/PollVoteWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Contact voted on a poll
  profile_picture_updated:
    post:
      requestBody:
        content:
          application/json:
            examples:
              removed:
                value:
                  event: profile_picture_updated
                  remove: true
                  timestamp: 1777180800
              updated:
                value:
                  event: profile_picture_updated
                  picture_id: '12345678901'
                  remove: false
                  timestamp: 1777180605
            schema:
              "$ref": "#/components/schemas/ProfilePictureUpdatedWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Own profile picture changed or removed
  typing:
    post:
      description: "`mode` is `text` for typing, `voice` when recording a voice message."
      requestBody:
        content:
          application/json:
            examples:
              composing:
                value:
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: typing
                  mode: text
                  state: composing
              paused:
                value:
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: typing
                  mode: text
                  state: paused
              recording_voice:
                value:
                  chat_id: 15550001234@s.whatsapp.net
                  contact_id: 15550001234@s.whatsapp.net
                  event: typing
                  mode: voice
                  state: composing
            schema:
              "$ref": "#/components/schemas/TypingWebhook"
      responses:
        '200':
          description: acknowledged
      security:
      - bearerAuth: []
      summary: Contact started or stopped typing
