# Webhook Integration

Receive, verify, and deduplicate WinkPG platform events at your own HTTPS endpoint.

**Category:** Integration

**Last reviewed:** 4 August 2026

# Webhook Integration

WinkPG delivers asynchronous platform events (card transaction lifecycle, hosted payment page completion, invoice lifecycle, ACH settlement status, merchant creation, and more) to an HTTPS endpoint you control. This guide covers the wire format, the signature scheme, replay protection, and delivery semantics so your receiver validates and deduplicates events correctly.

The authoritative list of event types is the event catalog in the portal (**Notifications** then **Event Types**). Every type it lists is one the platform publishes today, and each entry carries a sample payload for that type. Build against the catalog rather than against a type name you have seen elsewhere.

### Card transaction lifecycle events

`Transaction.Authorized`, `Transaction.Declined`, `Transaction.Captured`, `Transaction.Voided`, `Transaction.Reversed`, `Transaction.Failed` and `Transaction.Settled` cover a card payment's progress. Four things to know before wiring order fulfilment to them:

- **`Transaction.Captured` is not a funds signal.** Capture marks a transaction ready for settlement; funds move when the batch settles, which is what `Transaction.Settled` reports.
- **`Transaction.Settled` is the funds signal, and it arrives hours later.** Card settlement is a batch process, so this event lands well after the capture, and its `OccurredAtUtc` is when the row settled rather than when the notification was sent. `GatewayBatchId` and `ProcessorBatchId` let you reconcile a delivery against a settlement batch. ACH does not settle through this pipeline: use `Transaction.AchStatusChanged` for those. If a settlement batch is rolled back and the transaction settles again later, a second event arrives with a different batch id.
- **`Declined` and `Failed` are different outcomes.** `Declined` means the payment was refused (the issuer, the processor, or a fraud or policy rule said no), so retrying the same card unchanged will not help. `Failed` means the gateway could not process the request at all, so the payment was never decided and a retry may succeed.
- **A partial reversal is identifiable.** On `Transaction.Reversed`, `IsPartialReversal` is `true` and `AuthorizedAmount` minus `CumulativeReversedAmount` is the amount still authorized. Each reversal of a transaction delivers its own event.

## Configuring a destination

A webhook receiver is registered as a destination, scoped to a tenant, reseller, or merchant. Each destination carries:

- **URL**: the HTTPS endpoint that receives `POST` requests.
- **Secret key**: a per-destination shared secret used to sign every request. Required for production destinations; optional for sandbox. Stored encrypted at rest.
- **Subscriptions**: the set of event types the destination wants.

Rotate the destination secret on a defined cadence (90 days is a reasonable default). Rotation is non-disruptive: validate against both the previous and current secret during the rotation window.

## Request format

Every delivery is an HTTPS `POST` with `Content-Type: application/json`. The body is a JSON envelope:

```json
{
  "id": "<unique event id, GUID>",
  "type": "HostedPaymentPage.Transaction.Completed",
  "version": 1,
  "createdUtc": "2026-05-06T18:42:11.193Z",
  "tenantId": "<guid>",
  "merchantId": "<guid>",
  "correlationId": "<guid>",
  "data": { }
}
```

Use the `X-WinkPG-Delivery-Id` header as your idempotency key: it is identical on every retry and redelivery of the same delivery, and distinct for each endpoint the same event reaches. The envelope `id` (also sent as `X-WinkPG-Event-Id`) identifies the underlying event, so key on that one instead when a side effect must run at most once however many of your endpoints receive the event. The `correlationId` matches the id emitted in WinkPG logs and metrics for the originating operation, which makes cross-system tracing straightforward.

## Signature scheme

Every request is signed with HMAC-SHA256 using the destination secret. The signature header is versioned:

```
X-WinkPG-Signature: v1=sha256:<hex-digest>
```

The signed input is the timestamp header, a literal `.`, then the exact raw request body:

```
"<X-WinkPG-Timestamp>.<raw request body>"
```

Compute `HMAC_SHA256(secret_key, signature_input)` and compare it to the received digest using a constant-time comparison (`hmac.compare_digest` in Python, `crypto.timingSafeEqual` in Node) to avoid timing attacks. Check the `v1=sha256:` prefix before parsing the digest; the prefix exists so future algorithm upgrades do not break existing receivers.

## Replay protection

Two defenses work together:

1. **Timestamp validation**: reject requests whose `X-WinkPG-Timestamp` is outside a tolerance window (plus or minus 5 minutes is recommended). The timestamp is part of the signed input, so an attacker cannot alter it without invalidating the signature.
2. **Deduplication**: WinkPG delivers at least once, so the same request may be redelivered. Record processed `X-WinkPG-Delivery-Id` values (a database row keyed on that id, or a Redis `SETNX` with a generous TTL) and short-circuit duplicates with `200 OK`. Store the id in the same transaction as the side effect so a partial failure rolls back both. Headers have never been part of the signed input, so this header is additive and leaves signature verification unaffected.

## Response expectations

| Your receiver responds | WinkPG behavior |
|---|---|
| `2xx` | Success; no retry |
| `4xx` other than `408` or `429` | Permanent failure; no retry |
| `408`, `429`, or `5xx` | Transient; retried with exponential backoff |
| No response within timeout | Transient; retried with exponential backoff |

Aim to acknowledge within 5 seconds. If processing is expensive, validate the signature, enqueue the raw body, return `200 OK`, then process asynchronously.

## Security checklist

- Verify the signature with a constant-time compare.
- Reject a missing or wrong `v1=sha256:` prefix.
- Validate the timestamp against the current clock.
- Deduplicate on `X-WinkPG-Delivery-Id` (use the event id instead only when a side effect must run at most once across every endpoint receiving the event).
- Acknowledge with `2xx` only after the side effect is committed or durably enqueued.
- Never log the secret key; rotate it on a cadence.

## See also

- [All documentation](https://docs.winkpg.io/llms.txt): the machine-readable index of every public page on this site.
