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

# Create a webhook subscription

> Subscribes an HTTPS endpoint to the events you choose. The response includes `signing_secret`, which is shown only this once. Store it securely; you need it to verify webhook signatures. To change the secret, create a new subscription and delete the old one.

New subscriptions start `active` unless you set `status: disabled`. Leave out `phone_number_filter` to get events for all of your numbers, including numbers you add later.

The URL must use HTTPS and resolve to a public address.

Errors worth handling (all `400`):
- `UNSUPPORTED_WEBHOOK_EVENT`: an event type isn't recognized.
- `FEATURE_NOT_ENABLED`: an event type is recognized but can't be subscribed to.
- `WEBHOOK_URL_INVALID`, `WEBHOOK_URL_UNSAFE` or `WEBHOOK_URL_UNRESOLVABLE`: the URL isn't HTTPS, points to a private address, or doesn't resolve.




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/webhook-subscriptions
openapi: 3.1.0
info:
  title: Blujay API
  version: 1.0.0
  description: >-
    The Blujay API lets you send and receive iMessage, RCS and SMS messages from
    your

    Blujay phone numbers, manage contacts, and receive real-time webhooks.


    Every response uses the same JSON envelope and carries a `trace_id` you can
    share

    with support. Authenticate with your workspace API key.
servers:
  - url: https://api.blujay.io
    description: Production
security:
  - PublicApiKey: []
tags:
  - name: Auth
    description: Check which workspace and credential a request is using.
  - name: Chats
    description: >-
      Direct and group conversations between your Blujay numbers and your
      contacts.
  - name: Messages
    description: Send, read, edit, unsend and retry messages.
  - name: Phone Numbers
    description: >-
      Your Blujay numbers, whether each can send right now, and their live
      sending limits.
  - name: Capability
    description: >-
      Check whether a phone number can be reached by iMessage, RCS or SMS before
      you send.
  - name: Contacts
    description: Create, find, update, tag, assign and opt out contacts.
  - name: vCards
    description: >-
      Saved contact cards that you can send in a message. Recipients get a
      native contact card over iMessage, SMS or RCS.
  - name: Team Members
    description: People in your workspace you can assign contacts to.
  - name: Workflows
    description: Enroll contacts in workflows you start manually.
  - name: Attachments
    description: >-
      Upload files once and reuse them in messages, or download files from
      messages.
  - name: Webhooks
    description: Subscribe to events, inspect delivery attempts and verify signatures.
paths:
  /v1/webhook-subscriptions:
    post:
      tags:
        - Webhooks
      summary: Create a webhook subscription
      description: >
        Subscribes an HTTPS endpoint to the events you choose. The response
        includes `signing_secret`, which is shown only this once. Store it
        securely; you need it to verify webhook signatures. To change the
        secret, create a new subscription and delete the old one.


        New subscriptions start `active` unless you set `status: disabled`.
        Leave out `phone_number_filter` to get events for all of your numbers,
        including numbers you add later.


        The URL must use HTTPS and resolve to a public address.


        Errors worth handling (all `400`):

        - `UNSUPPORTED_WEBHOOK_EVENT`: an event type isn't recognized.

        - `FEATURE_NOT_ENABLED`: an event type is recognized but can't be
        subscribed to.

        - `WEBHOOK_URL_INVALID`, `WEBHOOK_URL_UNSAFE` or
        `WEBHOOK_URL_UNRESOLVABLE`: the URL isn't HTTPS, points to a private
        address, or doesn't resolve.
      operationId: createPublicWebhookSubscription
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookSubscriptionRequest'
            examples:
              messageLifecycle:
                summary: Message lifecycle webhooks
                value:
                  url: https://example.com/webhooks/blujay
                  event_types:
                    - message.accepted
                    - message.sent
                    - message.delivered
                    - message.read
                    - message.failed
      responses:
        '201':
          description: The new subscription, including its `signing_secret`.
          headers:
            X-Trace-ID:
              $ref: '#/components/headers/XTraceID'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    required:
                      - data
                    properties:
                      data:
                        $ref: '#/components/schemas/PublicWebhookSubscriptionCreate'
              examples:
                messageLifecycle:
                  summary: New subscription with its signing secret
                  value:
                    status: success
                    data:
                      subscription_id: whsub_abc123
                      url: https://example.com/webhooks/blujay
                      event_types:
                        - message.accepted
                        - message.sent
                        - message.delivered
                        - message.read
                        - message.failed
                      status: active
                      phone_number_filter:
                        mode: all
                      webhook_version: '2026-10-03'
                      signing_secret_hint: Y3OA==
                      created_at: '2026-10-14T17:01:00.000Z'
                      updated_at: '2026-10-14T17:01:00.000Z'
                      signing_secret: whsec_ZXhhbXBsZS1zZWNyZXQtZG8tbm90LXVzZS0xMjM0NTY3OA==
                    trace_id: trc_8f2a1c0e4b7d
                    timestamp: '2026-10-14T17:01:00.000Z'
        '400':
          $ref: '#/components/responses/WebhookSubscriptionRejected'
      security:
        - PublicApiKey: []
components:
  schemas:
    CreateWebhookSubscriptionRequest:
      type: object
      additionalProperties: false
      required:
        - url
        - event_types
      properties:
        url:
          type: string
          examples:
            - https://example.com/webhooks/blujay
          format: uri
          maxLength: 2048
          description: >-
            Your HTTPS endpoint, up to 2,048 characters. It must resolve to a
            public address; localhost and private, link-local and cloud metadata
            addresses are rejected.
        event_types:
          type: array
          description: The events to send to this endpoint (1–100).
          minItems: 1
          maxItems: 100
          items:
            $ref: '#/components/schemas/WebhookSubscribableEventType'
        phone_number_filter:
          $ref: '#/components/schemas/WebhookPhoneNumberFilter'
        webhook_version:
          $ref: '#/components/schemas/WebhookPayloadVersion'
          default: '2026-10-03'
          description: >-
            The payload version for this subscription's events. When omitted,
            new subscriptions use `2026-10-03`, which adds iMessage group typing
            with participants and otherwise matches `2026-09-02`. Existing
            subscriptions keep their version until you change it.
        status:
          $ref: '#/components/schemas/PublicWebhookSubscriptionStatus'
          default: active
          description: >-
            Defaults to `active`. A `disabled` subscription doesn't receive new
            events.
    SuccessEnvelope:
      type: object
      description: Wraps every successful response. The result is in `data`.
      required:
        - status
        - trace_id
        - timestamp
      properties:
        status:
          type: string
          enum:
            - success
        trace_id:
          $ref: '#/components/schemas/TraceId'
        timestamp:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
        client_request_id:
          type: string
          description: >-
            Your `X-Request-ID` header value, echoed back when you send one. Not
            included on the send responses (`POST /v1/chats`, `POST
            /v1/chats/{chat_id}/messages`, `POST
            /v1/messages/{message_id}/retry`); keep your own mapping from your
            request ID to the returned message ID there.
    PublicWebhookSubscriptionCreate:
      type: object
      additionalProperties: false
      required:
        - subscription_id
        - url
        - event_types
        - webhook_version
        - status
        - phone_number_filter
        - signing_secret_hint
        - signing_secret
        - created_at
        - updated_at
      properties:
        subscription_id:
          $ref: '#/components/schemas/WebhookSubscriptionId'
        url:
          type: string
          examples:
            - https://example.com/webhooks/blujay
          format: uri
        event_types:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        phone_number_filter:
          $ref: '#/components/schemas/WebhookPhoneNumberFilter'
        webhook_version:
          $ref: '#/components/schemas/WebhookPayloadVersion'
        status:
          $ref: '#/components/schemas/PublicWebhookSubscriptionStatus'
        signing_secret_hint:
          type: string
          examples:
            - Y3OA==
          minLength: 6
          maxLength: 6
          description: Last six characters of the signing secret.
        signing_secret:
          type: string
          examples:
            - whsec_ZXhhbXBsZS1zZWNyZXQtZG8tbm90LXVzZS0xMjM0NTY3OA==
          pattern: ^whsec_[A-Za-z0-9+/]+={0,2}$
          description: >-
            The secret for verifying webhook signatures. It is returned only in
            this create response, so store it securely. To get the signing key,
            remove the `whsec_` prefix and base64-decode the rest.
        created_at:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
        updated_at:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
    WebhookSubscribableEventType:
      type: string
      description: >
        Events you can subscribe to.

        - Outbound message events (`message.sent`, `message.delivered`,
        `message.read`, `message.failed`) cover messages sent through Blujay:
        the app, workflows, scheduled sends, integrations and the API.
        `message.accepted` is sent only for API sends. Messages sent outside
        Blujay aren't included.

        - `message.received` covers new incoming direct and group messages, not
        older messages Blujay imports later.

        - `phone_number.status_updated` is currently sent only when one of your
        numbers is replaced by a new dedicated number: the old number becomes
        `unavailable` and the new one `active`.
      enum:
        - message.accepted
        - message.sent
        - message.delivered
        - message.read
        - message.failed
        - message.received
        - reaction.added
        - reaction.removed
        - phone_number.status_updated
        - contact.tag_added
        - contact.assigned
        - chat.typing_indicator.started
        - chat.typing_indicator.stopped
        - participant.added
        - participant.removed
        - chat.group_name_updated
        - message.edited
        - message.unsent
    WebhookPhoneNumberFilter:
      description: >-
        Which of your numbers this subscription gets events for. `all` covers
        every number, including numbers you add later; `selected` covers only
        the `phone_number_ids` you list. Contact events are always sent for the
        whole workspace. Defaults to `all` on create; left unchanged on update
        if you omit it.
      oneOf:
        - type: object
          additionalProperties: false
          required:
            - mode
          properties:
            mode:
              type: string
              enum:
                - all
        - type: object
          additionalProperties: false
          required:
            - mode
            - phone_number_ids
          properties:
            mode:
              type: string
              enum:
                - selected
            phone_number_ids:
              type: array
              minItems: 1
              maxItems: 100
              uniqueItems: true
              items:
                type: string
                examples:
                  - phone_abc123
                pattern: ^phone_[A-Za-z0-9_-]+$
    WebhookPayloadVersion:
      type: string
      enum:
        - '2026-06-14'
        - '2026-08-01'
        - '2026-08-20'
        - '2026-09-02'
        - '2026-10-03'
      description: >
        The payload version for a subscription's events.

        - `2026-08-20` reports the service you asked for as `requested_service`
        and the service actually used as `service`.

        - `2026-09-02` adds message `parts`.

        - `2026-10-03` adds iMessage group typing with a `participants`
        snapshot, and `reply_to` on message events.


        Earlier versions keep their original payload shapes and never receive
        group typing. Each event uses the subscription's version at the time the
        event was created, so changing the version affects only later events.
    PublicWebhookSubscriptionStatus:
      type: string
      enum:
        - active
        - disabled
    TraceId:
      type: string
      examples:
        - trc_8f2a1c0e4b7d
      pattern: ^trc_[A-Za-z0-9_-]+$
    WebhookSubscriptionId:
      type: string
      examples:
        - whsub_abc123
      pattern: ^whsub_[A-Za-z0-9_-]+$
    WebhookEventType:
      type: string
      description: >-
        Every event type that can appear on a subscription or in a webhook. Some
        types are recognized only so older subscriptions keep working and may
        never be sent. You can add only the types in
        `WebhookSubscribableEventType`.
      enum:
        - message.accepted
        - message.sent
        - message.delivered
        - message.read
        - message.failed
        - message.received
        - reaction.added
        - reaction.removed
        - participant.added
        - participant.removed
        - chat.group_name_updated
        - phone_number.status_updated
        - contact.tag_added
        - contact.assigned
        - chat.typing_indicator.started
        - chat.typing_indicator.stopped
        - message.edited
        - message.unsent
    ErrorEnvelope:
      type: object
      description: >-
        Wraps every error response. Branch on `error.code`, and include
        `trace_id` when you contact support.
      required:
        - status
        - error
        - trace_id
        - timestamp
      properties:
        status:
          const: error
        error:
          $ref: '#/components/schemas/PublicError'
        trace_id:
          $ref: '#/components/schemas/TraceId'
        timestamp:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
        client_request_id:
          type: string
          description: Your `X-Request-ID` header value, echoed back when you send one.
    PublicError:
      type: object
      required:
        - code
        - message
        - type
        - http_status
        - retryable
        - doc_url
        - details
      properties:
        code:
          type: string
          description: Stable, machine-readable error code. Branch on this value.
          examples:
            - RATE_LIMIT_EXCEEDED
            - QUEUE_UNAVAILABLE
            - FEATURE_NOT_ENABLED
        message:
          type: string
          description: Human-readable explanation. It can change, so don't parse it.
        type:
          type: string
          description: Broad error category.
          examples:
            - validation
            - auth
            - idempotency
            - rate_limit
            - queue
            - feature_gate
        http_status:
          type: integer
          description: The HTTP status code of the response.
          enum:
            - 400
            - 401
            - 403
            - 404
            - 409
            - 413
            - 415
            - 422
            - 429
            - 500
            - 502
            - 503
        retryable:
          type: boolean
          description: Whether sending the same request again later can succeed.
        doc_url:
          type: string
          format: uri
          description: Link to documentation for this error code.
        details:
          type: array
          description: >-
            Extra structured context, such as the fields that failed validation
            or the limit you hit.
          items:
            type: object
            additionalProperties: true
        retry_after:
          type: integer
          minimum: 1
          description: >-
            Seconds to wait before retrying. Also sent as the `Retry-After`
            header.
        reset_at:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
          description: When the limit you hit resets.
  headers:
    XTraceID:
      description: >-
        Unique ID for this request. It matches `trace_id` in the body. Include
        it when you contact Blujay support.
      schema:
        $ref: '#/components/schemas/TraceId'
  responses:
    WebhookSubscriptionRejected:
      description: >
        The subscription wasn't saved:

        - `UNSUPPORTED_WEBHOOK_EVENT`: an event type isn't recognized.

        - `FEATURE_NOT_ENABLED`: an event type is recognized but can't be
        subscribed to.

        - `WEBHOOK_URL_INVALID`: the URL isn't HTTPS, includes credentials or a
        fragment, or is too long.

        - `WEBHOOK_URL_UNSAFE`: the URL points to a private or blocked address.

        - `WEBHOOK_URL_UNRESOLVABLE`: the URL's host name doesn't resolve.

        - `VALIDATION_ERROR`: another field is invalid.
      headers:
        X-Trace-ID:
          $ref: '#/components/headers/XTraceID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            unsafeUrl:
              value:
                status: error
                error:
                  code: WEBHOOK_URL_UNSAFE
                  message: Webhook URL must resolve to a public internet address
                  type: validation
                  http_status: 400
                  retryable: false
                  doc_url: https://docs.blujay.io/errors/webhook-url-unsafe
                  details: []
                trace_id: trc_8f2a1c0e4b7d
                timestamp: '2026-10-14T00:00:00.000Z'
            unsupported:
              value:
                status: error
                error:
                  code: UNSUPPORTED_WEBHOOK_EVENT
                  message: Unsupported webhook event type order.shipped
                  type: validation
                  http_status: 400
                  retryable: false
                  doc_url: https://docs.blujay.io/errors/unsupported-webhook-event
                  details: []
                trace_id: trc_8f2a1c0e4b7d
                timestamp: '2026-10-14T00:00:00.000Z'
            notSubscribable:
              value:
                status: error
                error:
                  code: FEATURE_NOT_ENABLED
                  message: Webhook event type contact.created is not available yet
                  type: validation
                  http_status: 400
                  retryable: false
                  doc_url: https://docs.blujay.io/errors/feature-not-enabled
                  details: []
                trace_id: trc_8f2a1c0e4b7d
                timestamp: '2026-10-14T00:00:00.000Z'
  securitySchemes:
    PublicApiKey:
      type: http
      scheme: bearer
      bearerFormat: blu_...
      description: >
        Your workspace API key, sent as `Authorization: Bearer blu_...`.
        Workspace admins can find it in the Blujay app under **API**.


        An API key has full access to your workspace and works on every
        endpoint. There is no test mode: every key sends real messages and
        changes real data, so keep it secret and use it only from your server.


        Each API key can make 600 read requests and 120 write requests per
        minute, plus 60 attachment requests per minute. Over a limit you get
        `429 RATE_LIMIT_EXCEEDED` with `Retry-After`.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.