# Mews POS Webhooks

## When to Use This Skill

* How do I receive Mews POS webhooks?
* How do I verify the Mews POS `X-Signature` header?
* Why is my Mews POS webhook signature verification failing?
* How do I handle `booking.status.updated` or `order.payments.added` events?

This skill covers the Mews **POS API** (restaurant and bar point of sale). Only the POS API signs webhooks; the Mews Connector (PMS) API is a different product.

## Verification (core)

Every request carries `X-Signature`: the standard Base64 (padded, 44 characters, no prefix) of HMAC-SHA256 over the **raw request body bytes**, keyed with your secret key as UTF-8 bytes. Nothing else is signed (no timestamp or id), so there is no replay window to check. Validate the header's format before comparing, and compare the whole string in constant time.

Node:

```javascript
const crypto = require('crypto');

function verify(rawBody /* Buffer */, signature, secret) {
  if (!secret || typeof signature !== 'string' || !/^[A-Za-z0-9+/]{43}=$/.test(signature)) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); // both 44 bytes
}

```

Python:

```python
import base64, hashlib, hmac, re

def verify(raw_body: bytes, signature: str | None, secret: str) -> bool:
    if not secret or not signature or not re.fullmatch(r"[A-Za-z0-9+/]{43}=", signature):
        return False
    expected = base64.b64encode(hmac.new(secret.encode(), raw_body, hashlib.sha256).digest())
    return hmac.compare_digest(signature.encode("ascii"), expected)

```

Comparing the Base64 **string** (not just the decoded bytes) matters: a value whose last character carries stray low bits decodes to the same bytes but is not what Mews sends, and Hookdeck's Mews POS source rejects it. Respond `401` for a missing, malformed or mismatching signature.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/mews-pos-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/mews-pos-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/mews-pos-webhooks/examples/fastapi/).

## Common Event Types

Dispatch on `meta.eventType`. `data.type` is the entity (`bookings`, `orders`, `products`) and is shared by several events, so don't key on it.

| Event | Description |
| --- | --- |
| `booking.status.updated` | A booking's status changed (for example to `seated`) |
| `order.state.updated` | An order's state changed |
| `order.status.updated` | An order's status changed |
| `order.total.updated` | An order's total changed |
| `order.payments.added` | A payment was added to an order |
| `product.availability.updated` | A product's availability changed |

> **Full reference**: [Mews POS webhooks](https://docs.mews.com/pos-api/events/webhooks). Retries, timeout and success codes are not documented, and the docs say a message "encapsulates all simultaneous events" without showing a multi-event body, so handlers should return `2xx` quickly and tolerate unknown events.

## Environment Variables

```bash
MEWS_POS_WEBHOOK_SECRET=your_secret_key   # The secret key used to sign webhooks, used as-is (UTF-8 bytes)

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 mews-pos --path /webhooks/mews-pos

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/mews-pos-webhooks/references/overview.md) - Mews POS webhook concepts and events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/mews-pos-webhooks/references/setup.md) - Configuration guide
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/mews-pos-webhooks/references/verification.md) - Signature verification details