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

# Activate a sender channel

> Switch on the account connected to this sender for one channel, so it sends and receives. This is the same switch as **Activate** on the dashboard's Accounts screen, with the same price and the same limits.

Connecting an account does not activate it: a newly connected account starts inactive, every send on it is refused, and it is not listed in the sender's `channels`.

**Billing.** Activating is billed as a channel connection. On a paid plan that is one full month charged from the balance at once, and the same amount every month while the connection stays active; the response states both as `chargedCents` and `monthlyCents`. The first connections a plan includes cost nothing, and so does re-activating a connection whose current month is already paid. A WhatsApp Business Account is never charged from the balance for its month: one beyond the ones the plan includes is billed on the subscription instead. The exception is a WhatsApp account switched off for non-payment, which settles its past-due fee from the balance when re-activated.

Calling this on a channel that is already active changes nothing and charges nothing. Not available with test-mode keys.



## OpenAPI

````yaml /openapi.json post /v1/senders/{senderId}/channels/{channel}/activate
openapi: 3.0.3
info:
  title: Zavu Unified Messaging Layer API
  version: 0.2.0
  description: >
    Unified multi-channel messaging API for Zavu.


    Supported channels:

    - **SMS**: Simple text messages

    - **WhatsApp**: Rich messaging with media, buttons, lists, CTA URL buttons,
    location requests, and templates

    - **Telegram**: Bot messaging with text, media, and interactive elements

    - **Email**: Transactional emails via Amazon SES


    Design goals:

    - Simple `send()` entrypoint for developers

    - Project-level authentication via Bearer token

    - Support for all WhatsApp message types (text, image, video, audio,
    document, sticker, location, contact, buttons, list, cta_url,
    location_request, reaction, template)

    - If a non-text message type is sent, WhatsApp channel is used automatically

    - 24-hour WhatsApp conversation window enforcement

    - Universal `to` field accepts phone numbers (E.164), email addresses, or
    numeric chat IDs (Telegram/Instagram/Messenger)
servers:
  - url: https://api.zavu.dev
security:
  - bearerAuth: []
paths:
  /v1/senders/{senderId}/channels/{channel}/activate:
    post:
      tags:
        - Senders
      summary: Activate a sender channel
      description: >-
        Switch on the account connected to this sender for one channel, so it
        sends and receives. This is the same switch as **Activate** on the
        dashboard's Accounts screen, with the same price and the same limits.


        Connecting an account does not activate it: a newly connected account
        starts inactive, every send on it is refused, and it is not listed in
        the sender's `channels`.


        **Billing.** Activating is billed as a channel connection. On a paid
        plan that is one full month charged from the balance at once, and the
        same amount every month while the connection stays active; the response
        states both as `chargedCents` and `monthlyCents`. The first connections
        a plan includes cost nothing, and so does re-activating a connection
        whose current month is already paid. A WhatsApp Business Account is
        never charged from the balance for its month: one beyond the ones the
        plan includes is billed on the subscription instead. The exception is a
        WhatsApp account switched off for non-payment, which settles its
        past-due fee from the balance when re-activated.


        Calling this on a channel that is already active changes nothing and
        charges nothing. Not available with test-mode keys.
      operationId: activateSenderChannel
      parameters:
        - $ref: '#/components/parameters/SenderIdParam'
        - $ref: '#/components/parameters/ActivatableChannelParam'
      responses:
        '200':
          description: The channel is active.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SenderChannelActivationResponse'
        '400':
          description: >-
            The channel has no activation (`sms`, `sms_oneway`, `voice`), or the
            key is a test-mode key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          description: The balance cannot cover what activating charges now.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                code: insufficient_balance
                message: >-
                  Not enough balance to activate this connection. Add funds and
                  try again.
        '403':
          description: The plan has no room for another active connection.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                code: plan_limit_reached
                message: >-
                  Your plan has no room for another active connection.
                  Deactivate one or upgrade.
        '404':
          description: Sender not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            `channel_not_connected`: the sender has no account for this channel.
            `connection_not_ready`: the account is connected but cannot carry
            messages yet.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                code: connection_not_ready
                message: >-
                  The telegram connection is not ready to carry messages yet.
                  Finish connecting it before activating.
      security:
        - bearerAuth: []
components:
  parameters:
    SenderIdParam:
      name: senderId
      in: path
      required: true
      schema:
        type: string
    ActivatableChannelParam:
      name: channel
      in: path
      required: true
      description: The channel to switch.
      schema:
        $ref: '#/components/schemas/ActivatableChannel'
  schemas:
    SenderChannelActivationResponse:
      type: object
      required:
        - sender
        - channel
        - activated
        - chargedCents
        - monthlyCents
      properties:
        sender:
          $ref: '#/components/schemas/Sender'
          description: >-
            The sender after the change. `channels` includes the channel once it
            is active.
        channel:
          $ref: '#/components/schemas/ActivatableChannel'
        activated:
          type: boolean
          description: >-
            True when this call switched the channel on. False when it was
            already active; nothing was charged.
        chargedCents:
          type: integer
          description: >-
            Cents taken from the balance by this call. Zero when the channel was
            already active, its month is already paid, or the plan includes it.
        monthlyCents:
          type: integer
          description: >-
            Cents this connection adds to each month while it stays active. Zero
            when the plan includes it.
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          example: invalid_request
        message:
          type: string
          example: Phone number is invalid
        details:
          type: object
          additionalProperties: true
    ActivatableChannel:
      type: string
      description: >-
        A channel whose connection has an on/off switch. `sms`, `sms_oneway` and
        `voice` are billed per message and per phone number, are always on, and
        have nothing to activate.
      enum:
        - whatsapp
        - telegram
        - instagram
        - messenger
        - email
    Sender:
      type: object
      required:
        - id
        - name
        - phoneNumber
      properties:
        id:
          type: string
          example: sender_12345
        name:
          type: string
          example: Primary sender
        phoneNumber:
          type: string
          description: Phone number in E.164 format.
          example: '+13125551212'
        channels:
          type: array
          items:
            type: string
          description: >-
            Channels this sender can actually send on right now: configured AND
            activated. Empty means the sender cannot send or receive anything
            yet: a phoneNumber alone does not enable SMS or voice, and a
            connected account that is not activated is left out, because every
            send on it is refused. Check this rather than inferring capability
            from phoneNumber or emailAddress, and turn a connected channel on
            with `POST /v1/senders/{senderId}/channels/{channel}/activate`.
          example:
            - sms
            - voice
        isDefault:
          type: boolean
          description: Whether this sender is the project's default.
          default: false
        webhook:
          $ref: '#/components/schemas/SenderWebhook'
        whatsapp:
          type: object
          description: >-
            WhatsApp Business Account information. Only present if a WABA is
            connected.
          properties:
            phoneNumberId:
              type: string
              description: WhatsApp phone number ID from Meta.
            displayPhoneNumber:
              type: string
              description: Display phone number.
              example: '+14155551234'
            paymentStatus:
              type: object
              description: Payment configuration status from Meta.
              properties:
                setupStatus:
                  type: string
                  description: Payment setup status (COMPLETE, NOT_STARTED, etc.).
                  example: COMPLETE
                methodStatus:
                  type: string
                  description: Payment method status (VALID, NONE, etc.).
                  example: VALID
                canSendTemplates:
                  type: boolean
                  description: >-
                    Whether template messages can be sent. Requires
                    setupStatus=COMPLETE and methodStatus=VALID.
        emailAddress:
          type: string
          description: From-address for the email channel, if configured.
          example: noreply@yourdomain.com
        emailReceivingEnabled:
          type: boolean
          description: Whether inbound email receiving is enabled for this sender.
          default: false
        emailCatchAllEnabled:
          type: boolean
          description: >-
            Whether catch-all receiving is enabled. When true (and
            emailReceivingEnabled is true), this sender receives email addressed
            to any local part at its domain, not just its own address. The
            original recipient is delivered in the message.inbound webhook's
            data.to.
          default: false
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    SenderWebhook:
      type: object
      description: Webhook configuration for the sender.
      required:
        - url
        - events
        - active
        - signatureVersion
      properties:
        url:
          type: string
          format: uri
          description: HTTPS URL that will receive webhook events.
          example: https://api.example.com/webhooks/zavu
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEvent'
          description: List of events the webhook is subscribed to.
        secret:
          type: string
          description: >-
            Webhook secret for signature verification. Only returned on create
            or regenerate.
          example: whsec_abc123...
        active:
          type: boolean
          description: Whether the webhook is active.
          default: true
        signatureVersion:
          $ref: '#/components/schemas/WebhookSignatureVersion'
    WebhookEvent:
      type: string
      description: >-
        Type of event that triggers the webhook.


        **Message lifecycle events:**

        - `message.queued`: Message created and queued for sending.
        `data.status` = `queued`

        - `message.sent`: Message accepted by the provider. `data.status` =
        `sent`

        - `message.delivered`: Message delivered to recipient. `data.status` =
        `delivered`

        - `message.read`: Message was read by the recipient (WhatsApp only).
        `data.status` = `read`

        - `message.failed`: Message failed to send. `data.status` = `failed`


        **Inbound events:**

        - `message.inbound`: New message received from a contact.
        `data.conversationId` is the inbox thread id (deep-link with
        `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`); it
        is `null` while the conversation row is still being created (the first
        message of a brand-new thread, or several near-simultaneous first
        messages), where `conversation.new` carries the id instead — `GET
        /v1/messages/{messageId}` always has it. Reactions are delivered as
        `message.inbound` with `messageType='reaction'`. When the contact
        replied to (quoted) an earlier message, `data.content` carries the reply
        context: `replyToMessageId`, `replyToProviderMessageId`, `replyToFrom`,
        `replyToText`, and `replyToMessageType`. `data.providerTimestamp` is the
        provider's original receive time in Unix milliseconds (the moment the
        channel received the message from the contact — WhatsApp, Telegram,
        Instagram, Messenger; `null` for SMS and email). Compare it against the
        top-level `timestamp` (when Zavu dispatched the webhook) to detect and
        ignore delayed deliveries. When the conversation was opened from a
        Click-to-WhatsApp ad or post, `data.referral` carries the ad attribution
        — including `ctwaClid`, the identifier Meta's Conversions API needs to
        credit a conversion back to that ad. WhatsApp only, and only on the
        first message of the thread: it is absent from every later message, so
        persist it when it arrives.

        - `message.unsupported`: Received a message type that is not supported


        **Broadcast events:**

        - `broadcast.status_changed`: Broadcast status changed (pending_review,
        approved, rejected, sending, completed, cancelled)


        **Other events:**

        - `conversation.new`: New conversation started with a contact. `data`
        carries `conversationId` (the inbox thread id — deep-link with
        `https://dashboard.zavu.dev/{locale}/inbox?conv={conversationId}`), the
        `phoneNumber` or `email` key, `channel`, `firstMessageId`,
        `firstMessageText`, and `profileName`.

        - `template.status_changed`: WhatsApp template approval status changed


        **Partner events:**

        - `invitation.status_changed`: A partner invitation's stored status
        changed: to `in_progress`, `completed`, `failed`, `cancelled`, or back
        to `pending` when it is resent from the dashboard. A change to the same
        status sends nothing, and expiry is not a stored change, so no event is
        sent when an invitation expires. Delivered to the project webhook (`POST
        /v1/invitations/webhook`) of the project that created the invitation; a
        parent project does not receive its sub-accounts' events. `data` carries
        `invitationId`, `clientName`, `clientEmail`, `connectionType`
        (`whatsapp_waba` or `messenger`), `previousStatus`, and `currentStatus`.
        On `completed` it also carries `senderId`, `connectedAccount`
        (`channel`, `id`, `name`) — the WhatsApp number or Facebook Page that
        was linked — and, for WhatsApp, `wabaAccountId`. On `failed` it carries
        `failureReason`; the invitation link stays usable, so a client can retry
        it.


        **Voice Agent events:**

        For every voice event, `data` carries `callId`, `direction`, `from`,
        `to`, `status`, `durationSeconds`, `endReason`, and
        `transcriptAvailable`. The terminal events (`call.completed`,
        `call.failed`) additionally carry `cost` — what the call was billed, in
        USD, combining telephony and the managed voice pipeline — and
        `currency`. They are dispatched after the call is charged, so `cost` is
        populated rather than zero; telephony can still be settling on an
        outbound call, in which case `GET /v1/calls/{callId}` holds the
        reconciled figure.

        - `call.initiated`: An outbound call was created and is dialing, or an
        inbound call was received. `data.status` = `ringing`

        - `call.answered`: The call was answered and the voice agent is
        connected. `data.status` = `in_progress`

        - `call.completed`: The call ended after a conversation. `data.status` =
        `completed`; `durationSeconds` and `endReason` describe how it ended,
        and `transcriptAvailable` indicates whether a transcript can be fetched.

        - `call.failed`: The call could not be completed (busy, no answer,
        canceled, or an error). `data.status` is the terminal status and
        `endReason` explains the cause.


        **Custom domain events:**

        - `domain.verified`: A custom email domain passed verification (DKIM,
        and SPF/DMARC/MAIL FROM if enhanced records are enabled)

        - `domain.failed`: A custom email domain failed verification or is
        partially verified
      enum:
        - message.queued
        - message.sent
        - message.delivered
        - message.read
        - message.status
        - message.failed
        - message.inbound
        - message.unsupported
        - broadcast.status_changed
        - conversation.new
        - template.status_changed
        - invitation.status_changed
        - call.initiated
        - call.answered
        - call.completed
        - call.failed
        - domain.verified
        - domain.failed
    WebhookSignatureVersion:
      type: string
      description: >-
        Which `X-Zavu-Signature` scheme this receiver is sent.


        - `v1`: `v1=HMAC_SHA256(secret, body)`. The scheme used before this was
        configurable. Existing webhooks stay on it until you move them.

        - `v2`: `v2=HMAC_SHA256(secret, "{t}.{body}")`. The current scheme, and
        the default for new senders. It signs the timestamp together with the
        body.

        - `v1+v2`: both signatures, sharing one `t`. The migration setting: a
        receiver reading either one works, so you can deploy and confirm your
        new verifier before switching over.


        Moving from `v1` straight to `v2` returns `400`. Set `v1+v2` first. See
        https://docs.zavu.dev/guides/receiving-messages/signature-migration
      enum:
        - v1
        - v1+v2
        - v2
      example: v2
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````