A webhook is an HTTP POST Topiic makes to a URL you control whenever something interesting happens — checkout outcomes, subscription state changes, payment results. The body is JSON; the headers carry an HMAC signature and an idempotency key.
Configure your endpoint
Section titled “Configure your endpoint”In the Topiic portal under Settings → API keys, find the row for your API key and:
- Set Endpoint URL to an HTTPS URL on your server. (HTTP is rejected outside development.)
- Tick the events you want to receive.
- Toggle Enabled on.
- Save.
You can change the URL or the subscribed events at any time. Changes apply to events produced after the save — events already enqueued retain the URL they were enqueued with.
What a delivery looks like
Section titled “What a delivery looks like”POST /webhooks/topiic HTTP/1.1Host: your-app.example.comContent-Type: application/jsonUser-Agent: Topiic-Webhooks/1.0Topiic-Event-Id: 4f8c2a14-7b3d-4e9c-8d6f-1a2b3c4d5e6fTopiic-Idempotency-Key: 4f8c2a14-7b3d-4e9c-8d6f-1a2b3c4d5e6fTopiic-Signature: t=1769472312,v1=8f2a1c4b9d6e3a7f0c5b8a1d2e4f6c9b7a3d5e8f0c1a4b7d9e2c6a8b3d5f7e9c
{ "id": "4f8c2a14-7b3d-4e9c-8d6f-1a2b3c4d5e6f", "type": "checkout.completed", "createdAt": "2026-05-27T10:25:12.123Z", "data": { "sessionId": "…", "externalRef": "cust_42", "memberId": "…", "paymentMethodId": "…", "subscriptionId": "…", "planId": "…" }}Headers
Section titled “Headers”| Header | Meaning |
|---|---|
Topiic-Event-Id | The event’s stable identifier. Same value across all delivery attempts for this event. Use it as your idempotency key. |
Topiic-Idempotency-Key | Mirrors Topiic-Event-Id. Provided separately so your generic webhook receiver can pick up “the” idempotency key without knowing the sender. |
Topiic-Signature | HMAC-SHA256 over <timestamp>.<body>. See Verifying signatures. |
Envelope
Section titled “Envelope”Every event body has the same outer shape:
{ "id": "<uuid>", "type": "<event.type>", "createdAt": "<ISO-8601 UTC>", "data": { /* event-specific */ }}The data shape depends on the event type — see the event reference.
Available event types
Section titled “Available event types”| Type | Fired when |
|---|---|
checkout.completed | A hosted-checkout session provisioned member + payment method + subscription. |
checkout.failed | A hosted-checkout session was cancelled, capture failed, or provisioning errored. |
subscription.paused | An active subscription has been paused. Billing is suspended. |
subscription.resumed | A paused subscription has been resumed. Billing restarts. |
subscription.cancelled | A subscription has been cancelled. Billing stops permanently. |
payment.succeeded | A recurring or retry charge was approved by the gateway. |
payment.failed | A recurring or retry charge was declined. |
payment.refunded | A charge was fully or partially refunded. |
More event types will be added over time — subscribe to only the types you handle, and treat unknown types as a no-op (don’t 500).
Fan-out behaviour
Section titled “Fan-out behaviour”Checkout events (checkout.*) deliver only to the API key whose signing secret produced the original deep link. Every other event fans out: it’s delivered to every API key on the merchant that has webhooks enabled and is subscribed to that event type. If three integrations all want to know when a subscription is cancelled, they each get their own delivery with their own signature.
What your handler must do
Section titled “What your handler must do”- Verify the signature — see Verifying signatures. Reject anything that doesn’t verify with
401. - Check the
Topiic-Event-Idagainst your event log. If you’ve seen it before, return200 OKand stop. Same event id may arrive more than once. - Apply the side effect in a transaction that also records the event id, so step 2 is meaningful on retry.
- Return a 2xx response within 10 seconds. Anything else triggers a retry on backoff. Return body is ignored —
200with empty body is ideal.
When deliveries happen
Section titled “When deliveries happen”The first attempt arrives within a few seconds of the triggering action. Outbound POSTs have a 10-second timeout — if your server doesn’t respond in time the attempt is counted as failed and a retry is scheduled.
When deliveries don’t happen
Section titled “When deliveries don’t happen”A delivery is silently skipped if:
- The API key has no webhook URL configured.
- The API key has webhooks disabled.
- The event type is not in the API key’s subscribed events list.
These are not errors — they’re a configured “I don’t want this”. No retries, no warning.
Inspecting delivery history
Section titled “Inspecting delivery history”Topiic stores the full delivery log: one row per attempt with the request body, response status, response body (capped at 8KB), and the next-attempt timestamp. Fetch it via GET /api/Webhooks/deliveries or the portal UI.
A manual replay (re-POST a recorded event) is available from the portal and via POST /api/Webhooks/deliveries/{id}/replay. Use it after fixing a receiver bug.
- Verifying webhook signatures — code samples for the algorithm.
- Retries & replay — backoff schedule and how to recover from a downed receiver.
- Event reference — payload shape per event type.