Webhook Integration
Webhooks let your systems receive real-time notifications when events happen on your Wink account — new bookings, cancellations, payment updates, and more. This guide walks you through setup and best practices.
Audience
Section titled “Audience”This guide is for developers integrating Wink with external systems such as property management systems (PMS), channel managers, CRMs, or custom dashboards.
How webhooks work
Section titled “How webhooks work”- You register a webhook URL on Wink.
- When an event occurs (e.g., a new booking), Wink sends an HTTP POST to your URL.
- Your server processes the payload and responds with a
200 OK.
Setting up a webhook
Section titled “Setting up a webhook”- Log in to your account (Extranet, Studio, or TripPay — all support webhooks).
- Navigate to
Applicationsand thenWebhooks. See Webhooks. - Click
Create webhook. - Enter a name (e.g., “PMS Booking Sync”).
- Enter your webhook URL — the HTTPS endpoint on your server.
- Select events — Choose specific events to subscribe to, or leave empty to receive all events.
- Toggle Enabled to on.
- Click
Save— the response shows your signing secret once; store it now.
Event types
Section titled “Event types”Wink publishes 70 webhook event types today across bookings, properties, accounts (managing entities) and inventory (room types, rate plans, master rates, add-ons, facilities, sales channels, promotions). Common ones:
| Category | Examples |
|---|---|
| Booking | booking.create, booking.cancelled, booking.refund.partial, booking.refund.full, booking.review.created |
| Property | property.created, property.status.updated, property.policy.updated |
| Inventory | room_type.updated, rate_plan.created, master_rate.updated, special_rate.created, sales_channel.created |
| Account | managing_entity.created, managing_entity.status.updated, managing_entity.manager.added |
The complete, generated list — with a description, who receives it, and a link to each event’s reference page — is the Webhook Events Catalog. The reference page for every event (JSON body, headers, retry policy) lives in the Webhooks API.
View every event type
What you receive
Section titled “What you receive”Every delivery is an HTTP POST to your webhook URL with Content-Type: application/json and this envelope:
{ "id": "0198a4f2-6b0e-7c1d-9a3e-2f4b8c6d1e0a", "type": "booking.create", "occurredAt": "2026-08-15T09:30:00Z", "ownerIdentifier": "3c6b1a5d-8e2f-4a0b-9c7d-6e4f0a8b2c51", "recipientRole": "SUPPLIER", "schemaVersion": 2, "object": { "...": "event-specific payload, e.g. BookingWebhookPayload" }}id— the event identifier; identical for every endpoint of your account that receives this event and for every retry. Use it as your idempotency key.type— the event type key (also sent as theWink-Event-Typeheader). Branch ontypeandschemaVersionto parseobject.object— a curated summary of the resource the event is about (identifiers, status, the fields you act on) pluslinks.self, the supplier-side canonical REST URL of the full resource. Fetch it with your own API credentials when you need more than the summary; if you receive the event as a reseller or travel agent, use the corresponding resource endpoint of your own API surface for the same identifier.
Every payload schema is documented per event in the Webhooks API reference.
Headers
Section titled “Headers”| Header | Meaning |
|---|---|
Wink-Version | Wire contract version, 2.0. |
Wink-Event-Id | Same as id in the body — your idempotency key. |
Wink-Delivery-Id | Unique per endpoint per event; changes only if you redeliver. |
Wink-Event-Type | Same as type in the body. |
Wink-Delivery-Attempt | 1-based attempt number for this delivery. |
Wink-Signature | HMAC signature — see below. |
Verifying signatures
Section titled “Verifying signatures”Every webhook has a signing secret (whsec_…) that Wink shows once, when you create the webhook or
rotate its secret. Store it like a password. Each delivery carries
Wink-Signature: t=1755250200,v1=5d41402abc4b2a76b9719d911017c592…where t is a Unix timestamp (seconds) and v1 is the lower-case hex HMAC-SHA256 of the string
t + "." + rawBody, keyed with your secret, and rawBody is the exact request body bytes as received —
do not re-serialise the JSON before verifying. For 24 hours after a secret rotation the header carries a
second v1= value signed with the previous secret; accept the delivery if any v1 matches.
Verify in four steps: parse t and every v1; recompute the HMAC over t.rawBody with your secret;
compare with a constant-time comparison; reject if |now − t| exceeds your tolerance (5 minutes recommended).
// Node.js (Express-style; make sure you have the RAW body, not a parsed object)import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWinkSignature(header, rawBody, secret, toleranceSeconds = 300) { const parts = Object.fromEntries(header.split(',').map((p) => p.split('=').map((s) => s.trim()))); const t = Number(parts.t); if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false; const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'); return header .split(',') .filter((p) => p.trim().startsWith('v1=')) .map((p) => p.trim().slice(3)) .some((v1) => v1.length === expected.length && timingSafeEqual(Buffer.from(v1, 'utf8'), Buffer.from(expected, 'utf8')));}// Javastatic boolean verify(String header, String rawBody, String secret, long nowSeconds, long toleranceSeconds) throws Exception { long t = Long.MIN_VALUE; List<String> signatures = new ArrayList<>(); for (String part : header.split(",")) { String[] kv = part.trim().split("=", 2); if (kv[0].equals("t")) t = Long.parseLong(kv[1]); else if (kv[0].equals("v1")) signatures.add(kv[1]); } if (t == Long.MIN_VALUE || Math.abs(nowSeconds - t) > toleranceSeconds) return false; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); byte[] expected = HexFormat.of().formatHex(mac.doFinal((t + "." + rawBody).getBytes(StandardCharsets.UTF_8))).getBytes(StandardCharsets.US_ASCII); return signatures.stream().anyMatch(v1 -> MessageDigest.isEqual(expected, v1.toLowerCase().getBytes(StandardCharsets.US_ASCII)));}Rotate the secret from the portal or with POST /api/managing-entity/{id}/webhook/{webhookId}/rotate-secret;
the response shows the new secret once, and the old one keeps verifying for 24 hours while you roll it out.
Retries and redelivery
Section titled “Retries and redelivery”- Respond with any
2xxwithin 10 seconds to acknowledge. Do the heavy work asynchronously. - A
5xx, a timeout,408or429is retried with backoff: after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours, then daily — 10 attempts over about 3 days — after which the delivery is marked dead. - Any other
4xxis treated as “you rejected this delivery” and is not retried. - Every event, delivery and attempt (status, response snippet) is visible under Applications > Webhooks
and through the API (
…/webhook/event/grid,…/webhook/delivery/grid). You can redeliver any delivery (POST …/webhook/delivery/{deliveryId}/redeliver, which starts a fresh retry series), redeliver every dead delivery of a webhook at once (POST …/webhook/{webhookId}/redeliver-dead), or cancel one. - Deliveries are retained for 30 days.
Test events
Section titled “Test events”Send yourself a synthetic webhook.test event from the portal or with
POST /api/managing-entity/{id}/webhook/{webhookId}/test. It is signed and delivered exactly like a real
event, so you can verify your endpoint, your signature check and your idempotency handling before subscribing
to live events.
Best practices
Section titled “Best practices”- Use HTTPS — Wink sends payloads to HTTPS endpoints only.
- Respond quickly — Return a
200 OKas soon as you receive the payload. Do any heavy processing asynchronously. - Idempotency — Your handler should be idempotent; deduplicate on
Wink-Event-Id. Wink retries when it does not receive a2xxresponse. - Validate the source — Verify the
Wink-Signatureheader (see Verifying signatures) before processing; reject anything that fails. - Logging — Log every webhook payload you receive. This makes debugging integration issues much easier.
Pausing and deleting
Section titled “Pausing and deleting”You can disable a webhook without deleting it. This pauses delivery so you can troubleshoot without losing your configuration. When you’re ready, toggle it back on.
Deleting a webhook permanently removes it. Any integration relying on that webhook will stop receiving notifications.
Further reading
Section titled “Further reading”- Webhook Events Catalog — Every event type, generated from the platform’s catalog.
- Webhooks API reference — Per-event payload schemas, headers, and the subscription/delivery management endpoints.
- Webhooks — Full reference for webhook management.
- Applications — Manage your API credentials.
- Developers > APIs — Full API documentation.
