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

# Register or update a webhook

> Register a webhook for operator SMS/voice events. At most **one
registration per (account, operator, network)** — `eventType` is not
part of that key, so registering a second event type for the same
operator+network replaces the first. Registering again for an existing
combination without `update: true` is a `409`.

`eventType: voice` (or `both`) registers successfully as long as the
operator/network combination itself is valid — there is no
registration-time check that voice is actually enabled, unlike `sms`.
Voice events only fire for a line whose active plan includes voice
support; a line on a plan with no voice allowance won't produce voice
events even with a registered webhook.

**`signingSecret` is returned only from this creation path** (format
`whsec_<64 hex chars>`) — no read endpoint ever returns it again. Store
it immediately; losing it means re-registering. Use it to verify the
`X-Spenza-Signature` header on every delivery (see the Webhooks guide).




## OpenAPI

````yaml /openapi.yaml post /api/v1.1/webhooks
openapi: 3.1.0
info:
  title: Spenza Partner API
  version: 3.0.0
  summary: >-
    Partner API for SIMs, eSIMs, plans, billing, users, groups and numbers —
    v1.1 surface.
  description: >
    The **Spenza Partner API** lets you manage SIMs, eSIMs, plans,
    subscriptions,

    billing, users, phone numbers, teams and device/SIM/user groups
    programmatically.


    All endpoints follow one consistent standard:


    - **Auth** — exchange your API `key` + `secret` for a bearer token, then
    send
      `Authorization: Bearer <token>` on every request.
    - **Envelopes** — success responses are `{ success, data, meta? }`; errors
    are
      `{ success: false, error: { code, message, details? } }`.
    - **Pagination** — list endpoints accept `page` (1-indexed) and `pageSize`
      (max 100); totals are returned in `meta`.
    - **Idempotency** — many write endpoints honor an optional `Idempotency-Key`
      request header so a retried call replays the original result instead of
      repeating the side effect.
    - **Async operations** — a handful of endpoints (eSIM provisioning, port-in,
      the `/api/v3/...` purchase variants, top-up) return `202` (`201` for
      top-up) with a `transactionId` + `statusEndpoint` instead of the finished
      resource. Poll
      `GET /api/v3/transactions/{transactionId}` (no auth required) until the
      transaction reaches a terminal status.

    One resource — **port-in submission** — is a documented exception: it lives
    at

    `POST /api/v3/port-in`, requires an Admin-tier account role rather than just
    a

    valid bearer token, and is **not** on the standard envelope (see that
    operation's

    description for its exact response shape).
  contact:
    name: Spenza API Support
    email: support@spenza.com
    url: https://spenza.com
  license:
    name: Proprietary
    url: https://spenza.com/terms
  x-logo:
    url: https://spenza.com/logo.png
    altText: Spenza
servers:
  - url: https://api.spenza.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: Exchange API credentials for a bearer token.
  - name: SIMs
    description: List, inspect, assign and manage SIMs.
  - name: Subscriptions
    description: Plan subscriptions attached to SIMs.
  - name: Plans & Catalog
    description: Purchasable plans and SIM products.
  - name: eSIM
    description: Provision eSIMs and fetch activation QR / install status.
  - name: Billing
    description: Invoices, credit balance and top-ups.
  - name: Users
    description: End users (employees) on your account that SIMs/devices are assigned to.
  - name: Orders & Transactions
    description: Order history and financial transactions.
  - name: Numbers & Port-in
    description: Check port-in eligibility and port existing numbers into Spenza.
  - name: Messaging
    description: Send outbound SMS from a provisioned number.
  - name: Webhooks
    description: Register endpoints for operator SMS/voice events and inspect deliveries.
  - name: Notifications
    description: In-account notifications and delivery preferences.
  - name: Device Groups
    description: Organize devices into named groups with spend/data limits.
  - name: SIM Groups
    description: Organize SIMs into named groups with spend/data limits.
  - name: User Groups
    description: Organize users (departments) into named groups with spend/data limits.
  - name: Devices
    description: The device catalog (phones/tablets) and their assignment to users.
  - name: Team Members
    description: Admins who manage your Spenza account (distinct from Users/end-users).
  - name: Async Status
    description: Universal status poll for any asynchronous (v3) operation.
paths:
  /api/v1.1/webhooks:
    post:
      tags:
        - Webhooks
      summary: Register or update a webhook
      description: |
        Register a webhook for operator SMS/voice events. At most **one
        registration per (account, operator, network)** — `eventType` is not
        part of that key, so registering a second event type for the same
        operator+network replaces the first. Registering again for an existing
        combination without `update: true` is a `409`.

        `eventType: voice` (or `both`) registers successfully as long as the
        operator/network combination itself is valid — there is no
        registration-time check that voice is actually enabled, unlike `sms`.
        Voice events only fire for a line whose active plan includes voice
        support; a line on a plan with no voice allowance won't produce voice
        events even with a registered webhook.

        **`signingSecret` is returned only from this creation path** (format
        `whsec_<64 hex chars>`) — no read endpoint ever returns it again. Store
        it immediately; losing it means re-registering. Use it to verify the
        `X-Spenza-Signature` header on every delivery (see the Webhooks guide).
      operationId: registerWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - operator
                - network
                - eventType
                - authentication
                - webhookUrls
              properties:
                operator:
                  type: string
                  example: SpenzaJ
                network:
                  type: string
                  example: T-Mobile
                eventType:
                  type: string
                  enum:
                    - sms
                    - voice
                    - both
                authentication:
                  type: object
                  required:
                    - type
                  description: >-
                    Credential your endpoint requires — Spenza presents it when
                    calling you. This is not a payload-signing scheme (see
                    `signingSecret`/`X-Spenza-Signature` for that).
                  properties:
                    type:
                      type: string
                      enum:
                        - basic
                        - bearer
                        - api_key
                        - none
                    username:
                      type: string
                      description: Required when type=basic.
                    password:
                      type: string
                      description: Required when type=basic.
                    token:
                      type: string
                      description: Required when type=bearer.
                    apiKey:
                      type: string
                      description: Required when type=api_key.
                    apiKeyHeader:
                      type: string
                      default: X-API-Key
                webhookUrls:
                  type: object
                  properties:
                    messageUrl:
                      type: string
                      format: uri
                      description: Incoming SMS.
                    callbackMessageUrl:
                      type: string
                      format: uri
                      description: SMS delivery-status callbacks.
                    voiceUrl:
                      type: string
                      format: uri
                      description: Incoming voice events.
                    callbackVoiceUrl:
                      type: string
                      format: uri
                      description: >-
                        Voice/trunk callbacks; payload includes a trunkType
                        field.
                    voiceStreamUrl:
                      type: string
                      description: >-
                        WS/WSS/HTTP(S) URL for live voice media — Spenza opens
                        an outbound WS, sends a JSON handshake, then binary
                        8kHz/16-bit/mono PCM frames.
                retryPolicy:
                  type: object
                  properties:
                    maxAttempts:
                      type: integer
                      minimum: 1
                      maximum: 10
                      default: 3
                    backoffStrategy:
                      type: string
                      enum:
                        - fixed
                        - exponential
                        - linear
                      default: exponential
                    initialDelaySeconds:
                      type: integer
                      minimum: 1
                      maximum: 60
                      default: 5
                    maxDelaySeconds:
                      type: integer
                      minimum: 60
                      maximum: 3600
                      default: 300
                status:
                  type: string
                  enum:
                    - active
                    - inactive
                  default: active
                description:
                  type: string
                update:
                  type: boolean
                  default: false
                  description: >-
                    Set true to overwrite an existing registration for this
                    operator+network. Coerced via class-transformer — sending
                    the string "false" is truthy and also overwrites; send a
                    real JSON boolean.
      responses:
        '201':
          description: Registered (or updated, if `update:true`).
          content:
            application/json:
              example:
                success: true
                data:
                  id: wh_69146d70ed68
                  operator: SpenzaJ
                  network: T-Mobile
                  eventType: sms
                  status: active
                  authentication:
                    type: bearer
                    token: '***ENCRYPTED***'
                  webhookUrls:
                    messageUrl: https://partner.example.com/hooks/sms
                    callbackMessageUrl: null
                    voiceUrl: null
                    callbackVoiceUrl: null
                    voiceStreamUrl: null
                  retryPolicy:
                    maxAttempts: 5
                    backoffStrategy: exponential
                    initialDelaySeconds: 5
                    maxDelaySeconds: 300
                  description: null
                  createdAt: '2026-07-10T10:00:00.000Z'
                  updatedAt: '2026-07-10T10:00:00.000Z'
                  signingSecret: >-
                    whsec_9b1f8c2e7a4d6053b8e1f2a9c4d7e6053b8e1f2a9c4d7e6053b8e1f2a9c4d70
        '400':
          description: >-
            Missing fields, or an unsupported operator/network/event-type
            combination.
          content:
            application/json:
              examples:
                missingFields:
                  value:
                    success: false
                    error:
                      code: MISSING_FIELDS
                      message: >-
                        Please fill in all the required fields to register this
                        webhook.
                      details:
                        missingFields:
                          - webhookUrls
                badEventTypeOrAuth:
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: >-
                        Please check the event type and authentication settings
                        you provided.
                      details:
                        field: eventType
                        allowed:
                          - sms
                          - voice
                          - both
                unknownOperatorNetwork:
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: That operator and network combination does not exist.
                notConfiguredForWebhooks:
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: >-
                        That operator and network combination is not configured
                        for webhooks.
                smsNotSupported:
                  value:
                    success: false
                    error:
                      code: VALIDATION_ERROR
                      message: >-
                        That operator and network combination does not currently
                        support SMS webhooks.
        '409':
          description: >-
            A webhook is already registered for this carrier without
            `update:true`.
          content:
            application/json:
              example:
                success: false
                error:
                  code: CONFLICT
                  message: >-
                    A webhook is already registered for this carrier and event
                    type — set the update flag to change it.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Bearer token obtained from `POST /api/v1.1/auth/token`.

````