Gareth Wilson Gareth Wilson

Guide to Mews POS Webhooks: Features and Best Practices

Published


Mews is a cloud platform for hotels and hospitality groups, and its POS API covers the restaurant and bar side: table bookings, orders, payments and menu products. Integrations such as kitchen displays, delivery platforms and accounting tools use POS API webhooks to learn when a party is seated, an order moves to the kitchen, a payment is added or a dish sells out. Only the POS API signs its webhooks. The Mews Connector API, used for the property management system, is a separate product with its own webhook scheme.

This guide explains how POS webhooks are registered, the six events and their status values, verifying the base64 X-Signature, and the parts of delivery that Mews leaves undocumented.

What are Mews POS webhooks?

You register a webhook endpoint with the POS API's Create webhook endpoint operation, and choose the entities and event types you want through a webhook subscription. Mews then posts JSON to your endpoint whenever a subscribed booking, order or product changes.

Each event identifies the entity and its new state, not the full record. Mews's guidance is to use the entity's ID with the relevant API operation when you need more detail. Mews also describes "unified event delivery": one message can contain all the simultaneous events for entities you've subscribed to, such as several bookings changing status at once.

Mews POS webhook features

FeatureDetails
Signaturebase64 HMAC-SHA256
Signature headerx-signature
BodyJSON
Event routingBody field meta.eventType

Not covered by the POS API webhook documentation: where the secret key comes from, how failed deliveries are retried, the timeout, which status codes count as success, and source IP addresses.

Events and payloads

| Event routing | Body field meta.eventType |

Status values are fixed lists. Bookings move through confirmed, seated, completed, cancelled and no_show. Order state covers draft, sent, paid, discarded, cart, pending_payment, open and open_web, while order status tracks fulfilment from received and confirmed through preparing, ready_for_delivery, dispatched, in_transit and delivered, or rejected.

This is the booking.status.updated sample from the webhook samples library:

{
  "data": {
    "id": "98656c4a-950d-4b19-b7ef-ad1744e92b53",
    "type": "bookings",
    "attributes": {
      "status": "seated",
      "updatedAt": "2025-02-06T09:41:30.465Z"
    }
  },
  "meta": {
    "eventType": "booking.status.updated"
  }
}

meta.eventType names the event. data.type names the entity, which several events share, so orders alone doesn't tell you whether the state, status, total or payments changed. data.attributes depends on the event: a new status, state or isAvailable value with updatedAt, tip and correction amounts for order.total.updated, or orderId and paymentId for order.payments.added.

Setting up Mews POS webhooks

  1. Call the POS API's Create webhook endpoint operation with your URL, for example https://example.com/webhooks/mews-pos.
  2. Set up the webhook subscription for the entities and events you need, such as booking.status.updated and order.status.updated.
  3. Get the secret key used for X-Signature. Mews's security page calls it "your secret key" without saying where it's issued, so check the endpoint registration response or ask your Mews contact. Store it as MEWS_POS_WEBHOOK_SECRET.

To use Hookdeck, create a Mews POS source with that key as its verification secret and register the source URL as the endpoint. For local testing:

npx hookdeck-cli listen 3000 mews-pos --path /webhooks/mews-pos

Securing Mews POS webhooks

X-Signature is a base64 HMAC-SHA256 of the raw body, keyed with your secret. Mews's webhook security page gives a Ruby example and recommends a constant-time comparison over the plain == in its first snippet.

Node.js verification

const crypto = require('node:crypto');

function decodeBase64(value) {
  if (typeof value !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(value)) return null;
  const bytes = Buffer.from(value, 'base64');
  return bytes.length > 0 && bytes.toString('base64').replace(/=+$/, '') === value.replace(/=+$/, '')
    ? bytes : null;
}

function verifySignature(rawBody, headers, secret) {
  if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
  const signature = headers['x-signature'];
  if (typeof signature !== 'string') return false;
  const key = Buffer.from(secret, 'utf8');
  const message = rawBody;
  const candidates = [signature];
  const expected = crypto.createHmac('sha256', key).update(message).digest();
  return candidates.some((candidate) => {
    const received = decodeBase64(candidate);
    if (!received) return false;
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });
}

The handler routes on meta.eventType, and accepts an array of events in case Mews groups several into one message:

const express = require('express');

const app = express();

app.post('/webhooks/mews-pos', express.raw({ type: '*/*' }), async (req, res) => {
  const secret = process.env.MEWS_POS_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500);
  if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);

  let body;
  try {
    body = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }
  if (!body || typeof body !== 'object' || Array.isArray(body)) return res.sendStatus(400);
  try {
    const events = Array.isArray(body) ? body : [body];
    for (const event of events) {
      await recordPosChange({
        eventType: event.meta?.eventType,
        entityType: event.data?.type,
        entityId: event.data?.id,
        attributes: event.data?.attributes ?? {},
      });
    }
  } catch {
    return res.sendStatus(503);
  }

  res.sendStatus(200);
});

recordPosChange should key on event type, entity ID and attributes.updatedAt, since there's no event ID, and ignore anything older than the state it already holds.

Mews signs no timestamp. Hookdeck's Mews POS source checks the same base64 HMAC, replies 200, and records a mismatch as failed verification.

Make Mews POS webhooks production-ready. Hookdeck Event Gateway verifies X-Signature, records every booking and order change, and retries your kitchen or accounting service under a policy you can see, which Mews doesn't document for the POS API.

Mews POS webhook limitations and pain points

Delivery behaviour is undocumented

The Problem: You can't tell whether a missed order.payments.added will be resent.

Why It Happens: The POS API's webhook pages don't describe retries, a timeout or which status codes count as success. The Connector API's FAQ does describe its own behaviour, but it covers a different product.

Workarounds:

  • Respond quickly with 200 and process afterwards.
  • Reconcile open orders and bookings through the POS API after an outage.

How Hookdeck Can Help: Hookdeck accepts each message immediately and applies a retry policy you define to your own service.

Undocumented secret source

The Problem: It isn't clear where the signing key comes from.

Why It Happens: The security page refers to "your secret key" without describing how it's issued.

Workarounds:

  • Check the Create webhook endpoint response and your integration settings.
  • Ask your Mews contact, and record which key belongs to which endpoint.

How Hookdeck Can Help: Once the key is set on a Hookdeck source, failed verifications appear in the request log, which quickly shows whether you have the right key.

Messages may contain several events

The Problem: Code that reads one meta.eventType per request can miss events.

Why It Happens: Mews says a message can contain all simultaneous events for subscribed entities, but every example shows a single event.

Workarounds:

  • Handle both a single object and an array, as the example does.
  • Log a few production messages at a busy time to confirm the real shape.

How Hookdeck Can Help: Hookdeck keeps each message in full, so you can see exactly how Mews batched events during service.

Events carry state, not detail

The Problem: An order.status.updated tells you the new status but not the order's items or customer.

Why It Happens: Each event includes the entity ID and changed attributes only.

Workarounds:

  • Fetch the order or booking from the POS API in a worker.
  • Cache product and order data to limit API calls.

How Hookdeck Can Help: Hookdeck transformations can add routing information, and filters can send events for each entity type to the service that needs them.

Best practices

Route on meta.eventType

Several events share data.type. Use the event type to choose a handler.

Ignore stale updates

Compare attributes.updatedAt with the stored value before applying a new status, which also absorbs duplicates. The webhook idempotency guide covers the storage side.

Keep the response fast

A busy service produces bursts of order events. Store them and reply, then process them in the background, as described in our guide to asynchronous webhook processing.

Don't confuse the two Mews APIs

Connector API webhooks aren't signed with X-Signature, so a POS verifier won't work for them, and the reverse.

Conclusion

Mews POS webhooks report booking, order, payment and product availability changes, signed with a base64 HMAC-SHA256 in X-Signature. Verify the raw body, route on meta.eventType, be ready for several events in one message, and fetch details from the API when the event's attributes aren't enough. Because the POS API doesn't document its retries, plan your own recovery path.

Hookdeck Event Gateway gives Mews POS events a durable record and a retry policy you control.

Get started with Hookdeck for free and handle Mews POS webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.