
# Webhook testing

Test with a sandbox endpoint before creating a production subscription. The simulator builds the same envelope, signs the same raw bytes and creates a real delivery record, so your receiver exercises production verification and retry behaviour without running an entire product flow.

## Recommended test sequence

1. Create a sandbox endpoint and copy its `whsec_…` secret into your receiver's secret store.
2. Choose one event from each family your integration handles.
3. Send a simulated delivery from the endpoint's **Testing** action.
4. Confirm your receiver verifies `X-Myaza-Signature-V2`, persists `id`, and returns `2xx` quickly.
5. Inspect the endpoint delivery history and correlate it with `deliveryId` or `X-Myaza-Delivery`.
6. Resend the delivery and confirm your business side effect happens only once.
7. Temporarily return a non-`2xx` response and confirm a retry is scheduled.
8. Rotate the secret and confirm both V2 `v1` signatures are accepted during the grace period.
9. If migrating from an alias, subscribe to the canonical name and alias together and confirm the shared event `id` prevents duplicate work.

## Catalogue-driven fixtures

`GET /api/v1/webhooks/catalogue` returns every supported event and its generated sample. Generate typed fixtures from this response rather than copying an invented schema into a test suite.

```bash
curl -H "Authorization: Bearer sk_test_…" \
  https://trust.myaza.app/api/v1/webhooks/catalogue
```

Samples show realistic public field shapes, but sample identifiers are not reusable API resources. Runtime events can contain additional optional fields documented on each event-family page.

## Idempotency assertion

Use the envelope `id`, not `deliveryId`, as the unique business key:

```sql
insert into received_myaza_events (event_id, event_type, payload)
values (:id, :type, :payload)
on conflict (event_id) do nothing;
```

A manual resend creates another delivery record but preserves the logical event ID. A compatibility alias also shares the canonical event's ID.

## Ordering assertion

Do not write a test that assumes a cross-type sequence such as `risk.signal.created` always arriving before `alert.created`. Delay or reverse two accepted events and confirm your handler either applies them by resource version/current API state or processes them independently.

## Production readiness checklist

- HTTPS endpoint uses a valid public certificate and does not redirect.
- Raw-body signature verification and replay rejection are covered by tests.
- Event IDs have a durable unique constraint.
- Slow processing runs outside the request.
- Unknown event types are safely stored or ignored and still acknowledged.
- Alerts exist for repeated delivery failures and dead-lettered deliveries.
- Secrets are environment-scoped, stored outside source code and rotation-tested.
