# Wix Webhooks

## When to Use This Skill

* How do I receive Wix webhooks in a self-hosted app?
* How do I verify Wix webhook signatures (the JWT)?
* Where do I get my Wix public key to verify webhooks?
* How do I handle `wix.ecom.v1.order_created` / `order_approved` / `order_canceled` events?
* Why is my Wix webhook JWT verification failing?

## How Wix Webhooks Work

Wix delivers each webhook as an HTTP POST whose entire request body is a signed JWT (RS256), signed by Wix. There are no `X-Wix-Signature`/HMAC headers and it is not Standard Webhooks — verification means validating the JWT with your app's public key.

* Get your public key from the app dashboard → Custom Apps → your app → Webhooks → "Get Public Key" (also under View ID & keys). It is per-app; there is no global JWKS endpoint.
* Verify against the raw, unparsed body. Re-serializing the JSON breaks the signature.
* The decoded JWT is a nested envelope: outer `{ data, iat, exp }` → `data` is a JSON string → parse it to `{ eventType, instanceId, data }` → parse the inner `data` string to get the entity/event payload.

## Verification (core)

Node.js — official `@wix/sdk` verifies (RS256, via `jose`) and decodes in one call. Pass the raw text body:

```javascript
import { AppStrategy, createClient } from '@wix/sdk';
import { orders } from '@wix/ecom';

const client = createClient({
  auth: AppStrategy({
    appId: process.env.WIX_APP_ID,
    publicKey: process.env.WIX_PUBLIC_KEY.replace(/\\n/g, '\n'), // PEM, or base64-encoded PEM
  }),
  modules: { orders },
});

// Register typed handlers up front...
client.orders.onOrderCanceled((event) => {
  console.log('Order canceled', event.metadata.entityId, 'event', event.metadata._id);
});

// ...then verify + dispatch the raw JWT body (throws if the signature/exp is invalid):
await client.webhooks.process(rawBody);

```

Python (no official server SDK) — verify the JWT manually with PyJWT + your public key:

```python
import json, jwt  # PyJWT

def verify_and_decode(raw_body: bytes, public_key: str) -> dict:
    decoded = jwt.decode(raw_body, public_key, algorithms=["RS256"])  # raises on bad signature/exp
    event = json.loads(decoded["data"])          # -> { eventType, instanceId, data: "<json>" }
    event["entity"] = json.loads(event["data"])  # inner event/entity payload (has id, entityId, ...)
    return event

```

> For complete handlers with route wiring, event dispatch, deduplication, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/wix-webhooks/examples/express/) — Node.js + `@wix/sdk`
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/wix-webhooks/examples/nextjs/) — App Router + `@wix/sdk`
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/wix-webhooks/examples/fastapi/) — manual PyJWT verification

## Common Event Types

Event type strings follow `wix.<product>.<version>.<entity>_<action>`. Configure each one on the Webhooks page of your app dashboard.

| Event Type | Triggered When |
| --- | --- |
| `wix.ecom.v1.order_created` | A new eCommerce order is created |
| `wix.ecom.v1.order_approved` | An order is approved (e.g. payment authorized) |
| `wix.ecom.v1.order_updated` | An order is updated |
| `wix.ecom.v1.order_canceled` | An order is canceled |
| App instance events | Lifecycle events such as your app being installed or removed from a site (App Instance Installed / Removed) — confirm the exact event identifier in the Wix app dashboard's Webhooks catalog, as the wire encoding differs from the eCommerce FQDN form above. |

See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/wix-webhooks/references/overview.md) for more events and payload structure. For the full list, browse events alongside their API methods in the [Wix API Reference](https://dev.wix.com/docs/api-reference).

## Environment Variables

```bash
WIX_APP_ID=your-app-id            # OAuth page of your app dashboard
WIX_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"  # Webhooks > Get Public Key

```

Store the PEM public key on one line with `\n` escapes (the examples convert them back to newlines). A base64-encoded PEM also works with `@wix/sdk`.

## Delivery, Retries & Idempotency

* Respond 200 within ~1250 ms or Wix retries — up to 12 more times over hours (1 min → 12 hours).
* Because of retries, events arrive out of order and duplicated. Dedupe on the event ID (`event.metadata._id` via the SDK, or the inner payload's `id` when verifying manually) and return 200 fast; do slow work asynchronously.
* Some legacy webhooks send only changed fields, not the full entity — GET the entity if you need the full object.

## Local Development

```bash
# Start a tunnel (no account needed) and forward to your local handler
npx hookdeck-cli listen 3000 wix --path /webhooks/wix

```

Use the printed HTTPS URL (path `/webhooks/wix`) as the Callback URL when creating the webhook in your app dashboard.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/wix-webhooks/references/overview.md) - What Wix webhooks are, common events, payload structure
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/wix-webhooks/references/setup.md) - App dashboard configuration, getting your public key
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/wix-webhooks/references/verification.md) - JWT verification details, SDK vs manual, gotchas