# Test signed webhooks

Test the receiving application and its signature check, not just the vendor's event list. The [browser signup example](../../cookbook/browser-signup/README.md) is a complete runnable example using `@volter/twin-stripe@3.0.1` and the real Stripe SDK.

## Register the receiver and prove delivery

The app is a World-owned process. After its listener starts, it calls `stripe.webhookEndpoints.create` with that exact local callback URL and `enabled_events: ['customer.created']`. It retains the secret returned by that creation in process memory. It becomes ready only after registration succeeds. No real vendor credential is used.

Signup calls `stripe.customers.create`. The twin records the customer, renders the vendor's event and signs its callback. The receiver reads the raw body, passes it and `Stripe-Signature` to `stripe.webhooks.constructEvent`, deduplicates by event ID, then reports receipt on the account page. The browser test asserts that status and separately sends an invalid signature to prove refusal. Do not synthesize success for the customer write in a handler.

## Account for the clock

The tutorial freezes the World at `2026-01-15T12:00:00Z`; signatures use that clock. A wall-time SDK age check would reject an intentionally backdated signature. The example supplies tolerance `0` to disable that age check while keeping HMAC verification. Production code should retain its usual tolerance; freezing a simulated clock does not justify weakening a production receiver.

## Keep the boundary explicit

| Boundary | What to assert |
|---|---|
| Vendor write | SDK create/read and World log entry |
| Callback routing | Registered URL is the current app endpoint |
| Verification | Correct signature accepted; incorrect signature returns 400 |
| Application result | Rendered account confirms the expected event/object |
| Duplicate processing | Deduplicate by event ID; the example stores one event per ID |

The example exercises one customer callback. It does not establish delivery retries, ordering guarantees, Connect events, payment callbacks or another vendor's webhook semantics. Inspect that release's manifest and README before extending it. Keep callbacks local unless the workflow deliberately requires a reachable endpoint; the [full-stack guide](./run-a-full-stack.md) owns service lifetime.
