Contact support

How-to

Add a Webhook notification

Configure webhook notifications in the MERGEPORT Controller and securely sign and verify them with Standard Webhooks.

Topic
Integrations
Last reviewed

This guide explains how to configure a webhook notification and securely verify incoming MERGEPORT webhooks.

Webhook notifications allow MERGEPORT to send real-time events to external systems via HTTP. They are commonly used to forward incoming orders to third-party services, but can also be used for other integrations such as POS systems, monitoring tools, or custom workflows.

Prerequisite / Setup

Webhook notifications cannot be used in parallel with other active POS integrations. If a POS integration is already enabled for a site, the webhook notification will not receive order events.

Configure the webhook

All configuration takes place in the MERGEPORT Controller.

  1. Open the Integration Settings Navigate to the restaurant/site you want to configure.
  2. Open “Connect additional services” Enter the public HTTPS address of your webhook endpoint under Webhook URL.
  3. Enter the Secret Key The same secret must be stored securely in the receiving system. Do not publish or log it.
  4. Select the Webhook Signing Mode Use Standard Webhooks for new integrations. Legacy remains available for existing integrations.
  5. Configure forwarding You can optionally forward only accepted orders or all status changes.
  6. Save and test the configuration Trigger a test order from an ordering platform or staging environment to confirm that the endpoint receives it correctly.

Legacy and Standard Webhooks

In Legacy mode, or when no signing mode is configured, MERGEPORT continues to send the secret in the HTTP Authorization header. Standard Webhooks headers are not sent in this mode.

In Standard Webhooks mode, the Secret Key is required. MERGEPORT uses it only to calculate the signature and does not send it in the Authorization header.

Each request then contains:

  • webhook-id: unique delivery ID; it remains unchanged across retries.
  • webhook-timestamp: Unix timestamp in seconds.
  • webhook-signature: HMAC-SHA256 signature formatted as v1,<base64>.

The signature is calculated over this exact string:

{webhook-id}.{webhook-timestamp}.{raw-request-body}

Verify the signature against the unchanged bytes of the HTTP request body. Do not parse or re-serialize the JSON before checking the signature. The following Node.js example therefore expects rawBody as a Buffer and rejects incomplete, expired, or incorrectly signed requests:

const crypto = require("crypto");

function verifyWebhook(headers, rawBody, secret, toleranceSeconds = 300) {
  const id = readSingleHeader(headers, "webhook-id");
  const timestamp = readSingleHeader(headers, "webhook-timestamp");
  const signature = readSingleHeader(headers, "webhook-signature");

  if (!id || !timestamp || !signature || !Buffer.isBuffer(rawBody)) return false;

  const timestampSeconds = Number(timestamp);
  const nowSeconds = Math.floor(Date.now() / 1000);
  if (
    !/^\d+$/.test(timestamp) ||
    !Number.isSafeInteger(timestampSeconds) ||
    timestampSeconds <= 0 ||
    Math.abs(nowSeconds - timestampSeconds) > toleranceSeconds
  ) return false;

  const signedContent = Buffer.concat([
    Buffer.from(`${id}.${timestamp}.`, "utf8"),
    rawBody,
  ]);
  const expected = Buffer.from("v1," + crypto
    .createHmac("sha256", secret)
    .update(signedContent)
    .digest("base64"), "utf8");
  const received = Buffer.from(signature, "utf8");

  return received.length === expected.length &&
    crypto.timingSafeEqual(received, expected);
}

function readSingleHeader(headers, name) {
  const value = headers[name];
  return typeof value === "string" ? value : undefined;
}

Only after successful signature verification, atomically record the webhook-id as processed and process the same ID once. This protects the integration against delayed replays and duplicate processing.