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

# Transactional template sends

> Send approved Utility WhatsApp templates by phone or external recipient id from external systems.

Transactional template sends let trusted external systems trigger expected WhatsApp messages through Switchbord. The endpoint is designed for Utility messaging such as payment receipts, instalment receipts, booking confirmations, vouchers, and other customer-expected operational events.

<Warning>
  This endpoint is not a bulk marketing API. Switchbord validates the caller workspace, template status, and template category before queueing a send. Use approved `UTILITY` templates for transactional flows.
</Warning>

## Endpoint

```text theme={null}
POST /api/v1/template-sends
```

The endpoint lives on the API app. Hosted workspaces call:

```text theme={null}
https://api.switchbord.ai/api/v1/template-sends
```

Self-hosted deployments should replace the host with their API domain.

## Authentication

Use a Switchbord API key scoped to the workspace. The workspace is resolved from the API key; do not send workspace ids in the request body.

```http theme={null}
Authorization: Bearer sk_live_...
Content-Type: application/json
Idempotency-Key: receipt-event-123
```

You can also send the idempotency key as `idempotencyKey` in the JSON body. Prefer the header when the upstream system supports it.

## Request shape

```json theme={null}
{
  "templateName": "payment_receipt_link",
  "templateLocale": "it",
  "recipient": {
    "phoneE164": "+393331234567",
    "externalId": "traveller-123",
    "name": "Giulia Rossi"
  },
  "variables": {
    "body": {
      "1": "Giulia",
      "2": "R-1001",
      "3": "€ 240,00"
    },
    "buttons": {
      "url": [
        { "index": 0, "suffix": "R-1001" }
      ]
    }
  },
  "metadata": {
    "source": "booking-system",
    "eventType": "payment.receipt.created"
  }
}
```

| Field                  | Type   | Required                                                            | Notes                                                                                                               |
| ---------------------- | ------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `templateName`         | string | Yes                                                                 | Must resolve to an approved Utility template visible to the caller workspace.                                       |
| `templateLocale`       | string | No                                                                  | Meta locale such as `it`, `it_IT`, or `en_US`. If omitted, the template lookup uses the workspace default behavior. |
| `recipient.phoneE164`  | string | Yes unless contact/external lookup is supplied by a future contract | Phone in E.164 format.                                                                                              |
| `recipient.externalId` | string | No                                                                  | External customer id to persist on the resolved contact.                                                            |
| `recipient.name`       | string | No                                                                  | Display name for newly created/resolved contact context.                                                            |
| `variables`            | object | No                                                                  | Body/header/button values compiled into Meta template components.                                                   |
| `components`           | object | No                                                                  | Alias for `variables`; use one or the other.                                                                        |
| `idempotencyKey`       | string | Recommended                                                         | Prevents duplicate sends for the same upstream event.                                                               |
| `metadata`             | object | No                                                                  | Stored with the message for audit/debug context. Do not include secrets.                                            |

## Responses

A successful request queues an outbound `message.dispatch` job and returns the Switchbord identities for audit and reconciliation.

New sends return HTTP `201`:

```json theme={null}
{
  "data": {
    "contactId": "contact_123",
    "conversationId": "conversation_123",
    "messageId": "message_123",
    "clientMessageId": "receipt-event-123",
    "status": "queued",
    "reused": false,
    "idempotent": true
  }
}
```

If the same idempotency key is sent again, Switchbord returns HTTP `200` with the existing message identity and `reused: true` instead of creating a duplicate send.

## Variables

### Body variables

For positional templates, use 1-based numeric keys:

```json theme={null}
{
  "variables": {
    "body": {
      "1": "Giulia",
      "2": "R-1001",
      "3": "€ 240,00"
    }
  }
}
```

Named body variables are supported by Switchbord's internal transactional compiler for inbound webhook mappings, but the public API route currently accepts positional body variables only. Use numeric body keys or an array when calling `POST /api/v1/template-sends` directly.

### Dynamic URL button variables

For templates with a URL button such as `https://example.com/r/{{1}}`, supply the dynamic suffix by zero-based button index:

```json theme={null}
{
  "variables": {
    "buttons": {
      "url": [
        { "index": 0, "suffix": "R-1001" }
      ]
    }
  }
}
```

`text` and `suffix` are accepted aliases. The value must be non-empty.

### Document header variables

For templates with a DOCUMENT header, supply a public HTTPS link. `filename` is optional but recommended.

```json theme={null}
{
  "variables": {
    "header": {
      "document": {
        "link": "https://cdn.example.com/receipts/R-1001.pdf",
        "filename": "R-1001.pdf"
      }
    }
  }
}
```

Document links must use HTTPS. Invalid or non-HTTPS links are rejected before queueing.

## Error responses

| HTTP  | Error                           | Meaning                                                                       |
| ----- | ------------------------------- | ----------------------------------------------------------------------------- |
| `400` | `invalid_json`                  | Request body could not be parsed as JSON.                                     |
| `400` | `invalid_request`               | JSON parsed but did not match the endpoint schema.                            |
| `400` | `template_not_approved`         | The template exists but is not approved by Meta.                              |
| `400` | `template_category_not_allowed` | The template is not Utility; transactional sends are Utility-only by default. |
| `400` | `invalid_media_link`            | A document or image link is missing, invalid, or not HTTPS.                   |
| `400` | `missing_button_url_variable`   | A dynamic URL button variable is required but empty.                          |
| `401` | `missing_authorization`         | Missing `Authorization: Bearer ...` header.                                   |
| `403` | `invalid_or_insufficient_key`   | API key is invalid or does not have `data_write` scope.                       |
| `404` | `template_not_found`            | Template name/locale could not be resolved for the workspace.                 |
| `429` | API access denied               | Public API rate/security middleware denied the request.                       |

Idempotent reuses are not errors. They return HTTP `200` with `data.reused: true`.

## Operational notes

* The worker still owns the final Meta dispatch. The API response means the message was accepted into Switchbord's outbox, not necessarily delivered by Meta.
* Delivery transitions (`queued`, `sent`, `delivered`, `read`, `failed`) are visible on the message/conversation records and in webhook status events.
* Do not include raw card details, private receipt tokens, or long-lived secrets in template variables or metadata. Prefer short receipt ids or pre-signed URLs with appropriate expiry.

## Related guides

* [Receipt link guide](/guides/receipt-link-webhook)
* [PDF receipt guide](/guides/pdf-receipt-webhook)
* [Inbound webhooks](/api-reference/inbound-webhooks)
