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

# Webhook campaigns API

> Overview of the webhook campaign send surface, with dedicated keys, contract locking, lifecycle rules, and the full error catalog.

A webhook campaign is a standing integration. Your system calls Tars once per recipient, and Tars sends the campaign's approved WhatsApp template. Each call is charged individually against the campaign wallet. For creating and operating webhook campaigns in the dashboard, see [Run webhook campaigns](/docs/guides/campaigns/webhook-campaigns).

The surface has two operations, both taking the same request body.

* [Send a campaign message](/docs/developer/api/webhook-campaigns/send) delivers the template to one recipient and charges the wallet.
* [Validate a campaign call](/docs/developer/api/webhook-campaigns/validate) checks the call and quotes its price without sending or charging.

## Dedicated keys

Webhook campaigns use their own keys, not the organization API keys. Generate them on the campaign's **API Keys** tab: enter a key label, then select **Generate key**. Each key is bound to its campaign and lists its label, prefix, **Created**, and **Last used** dates. **Revoke** stops every integration that uses the key at once. The full key is shown once at creation.

## Contract locking

The **Contract** tab shows the exact contract for your campaign's template as a Postman collection, OpenAPI 3.1.0, and cURL. Tars pins the template version when the campaign is created. If the template changes after that, calls fail with `409 template_version_drift`.

While the campaign has at least one key, its template, parameters, and WhatsApp number cannot be edited. Revoke every key, or clone the campaign, to change them.

## Lifecycle

Campaign status drives two error codes that matter most for client logic. A `409 campaign_paused` is retryable, because a paused campaign can become active again. A `410 campaign_gone` means the campaign is cancelled or failed. That state is terminal, so a well-behaved client stops calling the endpoint.

The **Activity** tab records every call with its status, error code, request ID, and cost. A call to a paused campaign is recorded too, as a failed call with `campaign_paused`.

## Errors: RFC 9457 problem details

Failures on `/send` return `Content-Type: application/problem+json` with this body. The `/validate` operation answers with its own `ok`, `code`, and `errors` shape, and only the status matches the catalog.

| Field       | Description                                                                                                                                                                                                                         |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title`     | Short human-readable summary from the error catalog                                                                                                                                                                                 |
| `status`    | The HTTP status, repeated in the body                                                                                                                                                                                               |
| `detail`    | Specific explanation for this occurrence                                                                                                                                                                                            |
| `instance`  | The `tars_…` request ID, for support and log correlation                                                                                                                                                                            |
| `code`      | Machine-readable error code (table below)                                                                                                                                                                                           |
| `retryable` | `true` when retrying the same call can succeed                                                                                                                                                                                      |
| `errors`    | Present on `invalid_params` only. Per-field entries of `{ "field": "…", "code": "…" }`. The code is `template_param_missing`, `template_param_invalid`, `unknown_param`, `unsupported_template`, `invalid_e164`, or `invalid_media` |

Branch on `retryable`, not on the status number.

The codes `rate_limited`, `meta_rate_limited`, and `meta_unavailable` carry a `Retry-After` header in seconds. The code `campaign_paused` carries no header, and `channel_unavailable` does not always carry one. Use your own backoff when the header is missing.

| Status | Code                      | Retryable | Meaning                                                                                   |
| ------ | ------------------------- | --------- | ----------------------------------------------------------------------------------------- |
| `400`  | `campaign_mode_invalid`   | No        | The campaign is not a webhook campaign                                                    |
| `401`  | `unauthorized`            | No        | Missing or invalid `Authorization` header                                                 |
| `401`  | `invalid_key`             | No        | Invalid or revoked campaign key                                                           |
| `402`  | `insufficient_balance`    | No        | Campaign wallet cannot cover the quoted cost                                              |
| `403`  | `organization_inactive`   | No        | The organization is inactive                                                              |
| `403`  | `billing_locked`          | No        | Campaigns require an active paid plan                                                     |
| `403`  | `consent_revoked`         | No        | The recipient has opted out of WhatsApp                                                   |
| `403`  | `us_marketing_blocked`    | No        | A marketing template cannot be sent to a US (`+1`) number                                 |
| `403`  | `channel_test_mode`       | No        | The WhatsApp number is in test mode                                                       |
| `404`  | `not_found`               | No        | Unknown route or campaign                                                                 |
| `409`  | `campaign_paused`         | Yes       | The campaign is not active. Resume it, then retry                                         |
| `409`  | `template_version_drift`  | No        | The template changed since the version was pinned                                         |
| `409`  | `channel_unavailable`     | Yes       | The WhatsApp number is unavailable, for example because the provider paused sending on it |
| `410`  | `campaign_gone`           | No        | The campaign is cancelled or failed. Terminal: stop retrying                              |
| `422`  | `invalid_params`          | No        | Parameter validation failed, see `errors`                                                 |
| `422`  | `template_not_approved`   | No        | The template is not approved                                                              |
| `422`  | `channel_misconfigured`   | No        | The campaign's number or template setup is broken                                         |
| `422`  | `channel_reauth_required` | No        | The WhatsApp connection expired, reconnect the number                                     |
| `422`  | `meta_rejected`           | No        | WhatsApp rejected the message                                                             |
| `429`  | `rate_limited`            | Yes       | Too many sends for this key, honor `Retry-After`                                          |
| `429`  | `meta_rate_limited`       | Yes       | WhatsApp is rate limiting sends                                                           |
| `503`  | `meta_unavailable`        | Yes       | WhatsApp is temporarily unavailable                                                       |
| `500`  | `internal_error`          | No        | Unexpected server error, contact support with `instance`                                  |

## Rate limits and charges

Sends are limited to 120 per minute for each organization and campaign key pair, with a burst capacity of 200. Over-limit calls return `429` with code `rate_limited` and a `Retry-After` header. Validate calls are not rate limited.

A send is charged before it goes out, and a send that then fails is refunded automatically. Sends on a WhatsApp number in **Staging** mode are not charged.

## Related pages

* [Send a campaign message](/docs/developer/api/webhook-campaigns/send)
* [Validate a campaign call](/docs/developer/api/webhook-campaigns/validate)
* [Run webhook campaigns](/docs/guides/campaigns/webhook-campaigns)
* [Fund your campaign wallet](/docs/guides/campaigns/campaign-wallet)
* [API authentication](/docs/developer/authentication)
