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

# Test your integration with trigger

> Send signed test events for a Test number to your webhooks and the events API.

Trigger a named test event for a Test number to check how your integration
handles it. Polymorfa delivers the event to your webhooks and the events API in
the same format as production events, signed with your endpoint's signing
secret. You don't need a phone, a second person, or a real WhatsApp account.

Test events work only for Test numbers (`testMode: true`). A request for any
other number returns `400 Bad Request` and sends nothing.

## Before you start

* Your team has Test numbers access. See [What is Sandbox?](/sandbox/introduction).
* The project has a Test number. Use its session name in each request.
* You have a project key, or a team key, with the `sandbox:write` scope. Listing
  fixtures needs `sandbox:read`. Client tokens can't trigger test events.

## Trigger an event

Send the fixture name as `event` and the Test number as `session`:

```bash theme={null}
curl -X POST "https://api.polymorfa.com/messaging/testing/$PROJECT_ID/events" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "session": "my-test-number",
    "event": "message.received",
    "overrides": { "text": "hi", "from": "+15550100001" }
  }'
```

The API returns `202 Accepted`:

```json theme={null}
{
  "event": "message.received",
  "session": "my-test-number",
  "delivery": "generated",
  "eventId": "0199f1c2-7a4e-7c55-9d1e-3f0b8a2c6d10",
  "source": "test"
}
```

Webhook subscribers to `message.received` receive the event with the same
envelope and signature headers as any other event. Look it up by `eventId` with
`GET /platform/projects/{projectId}/events/{eventId}`; see [Event history](/api/events). Test events carry
`source: "test"` and `environment: "development"`, so you can tell them apart
from events produced by WhatsApp activity.

With the TypeScript SDK:

```typescript theme={null}
import { MessagingClient } from "@polymorfa/sdk";

const client = new MessagingClient({
  credential: { type: "apiKey", value: process.env.POLYMORFA_KEY! },
});

const result = await client.testing.triggerEvent(process.env.PROJECT_ID!, {
  session: "my-test-number",
  event: "message.received",
  overrides: { text: "hi" },
});
console.log(result.data.eventId);
```

## Fixtures

Each fixture name is the event type it produces.

| Fixture                | Produces                                     | Overrides                                           |
| ---------------------- | -------------------------------------------- | --------------------------------------------------- |
| `message.received`     | An inbound text or media message             | `text`, `from`, `pushName`, `mediaType`, `caption`  |
| `message.ack`          | A receipt for an outbound message            | `from`, `ackStatus`, `messageId`                    |
| `message.failed`       | An outbound send that failed                 | `from`, `failureReason`                             |
| `call.received`        | An incoming call offer                       | `from`, `video`                                     |
| `call.missed`          | An unanswered incoming call                  | `from`                                              |
| `call.ended`           | A finished call                              | `from`, `video`, `durationSeconds`, `callEndReason` |
| `session.status`       | A connection status change                   | `status`, `statusReason`                            |
| `template.status`      | A template approval or rejection             | `templateName`, `templateStatus`, `reason`          |
| `bansafe.enforcement`  | A BanSafe ban warning or enforcement outcome | `enforcementKind`                                   |
| `bansafe.risk_changed` | A BanSafe risk level change                  | `riskLevel`                                         |

List the fixtures and their overrides with
`GET /messaging/testing/{projectId}/events/fixtures`.

A fixture rejects an override it doesn't list with `400 Bad Request`.
Without overrides, each fixture uses these values:

* `message.received`: a text message from `+15550100001`. Set `mediaType` to
  `image`, `video`, `audio`, `document`, or `sticker` to send media metadata
  instead, with an optional `caption` for images, videos, and documents.
  Generated media events contain no downloadable file.
* `message.ack`: `ackStatus` is `delivered`. Also `read`, `played`, or `error`.
* `message.failed`: `failureReason` is `send_failed`.
* `session.status`: `DISCONNECTED` with `STREAM_ERROR`.
* `template.status`: `APPROVED`. Set `templateStatus` to `REJECTED` and an
  optional `reason` to test a rejection.
* `bansafe.enforcement`: `enforcementKind` is `cap_warning`. Also
  `cap_reached`, `timelock`, or `temporary_ban`.
* `bansafe.risk_changed`: `riskLevel` is `high`.

## What test events don't change

A test event reaches your webhooks and the events API only. It doesn't change
the Test number: a `session.status` event doesn't disconnect it, a
`bansafe.enforcement` event doesn't restrict it, and call events don't ring
Calls clients. Test events don't count toward campaign reply metrics.

Contact and message IDs in generated events are random. They don't refer to a
conversation, so you can't reply to them.

## Send a simulated message between Test numbers

To test a full round trip, including replies, send a real simulated message from
another connected Test number in the same project. Set `fromSession` on a
`message.received` request:

```json theme={null}
{
  "session": "my-test-number",
  "event": "message.received",
  "fromSession": "my-other-test-number",
  "overrides": { "text": "hi" }
}
```

The response has `"delivery": "simulated"` and `"eventId": null`. The
`message.received` event arrives shortly after as ordinary Test number
activity, with `source: "runtime"`, and you can reply to it. Both Test numbers
must be connected. `fromSession` accepts only the `text` override.

## Limits

Each project can trigger 30 test events per minute. Further requests return
`429 Too Many Requests` with a `Retry-After` header that gives the seconds to
wait. Every trigger is recorded in your team's [audit log](/console/audit-logs)
as `testing.event.trigger`.

## Retry safely

Send an `Idempotency-Key` header, up to 255 printable ASCII characters, to make
a trigger safe to retry. A repeated request with the same key and body gets the
same `eventId`, so your event history and webhooks receive the event once. A
different body with the same key creates a separate event.

```bash theme={null}
curl -X POST "https://api.polymorfa.com/messaging/testing/$PROJECT_ID/events" \
  -H "Authorization: Bearer $POLYMORFA_API_KEY" \
  -H "Idempotency-Key: order-flow-check-1" \
  -H "Content-Type: application/json" \
  -d '{"session": "test-a", "event": "message.received"}'
```

## Errors

| Status | Cause                                                                                                                                  |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | The session isn't a Test number in the project, the fixture or an override is invalid, or `fromSession` isn't a connected Test number. |
| `403`  | Your team doesn't have Test numbers access, or the request used a client token.                                                        |
| `404`  | The project doesn't exist in your team or doesn't match your key.                                                                      |
| `429`  | The project reached 30 test events per minute.                                                                                         |
| `502`  | Test event delivery or the access check is temporarily unavailable. Retry the request with the same `Idempotency-Key`.                 |
