Skip to content
Notification webhooks

Notification webhooks

This page is for a receiver of your own: n8n, Zapier, Make, Node-RED or a script. It describes the Generic JSON format and how to check the signature. For the notifier’s settings, see Notifications.

Set it up

  1. Go to Plugins > Webhook Notifiers and click + New Webhook Notifier.
  2. Choose the format Generic JSON.
  3. Enter your receiver’s address as the Webhook URL.
  4. Enter a Signing Secret: a long random value that your receiver also knows. For example, make one with openssl rand -hex 32.
  5. Click Create and restart MicroB2B.
  6. Click Send test notification on the notifier’s edit page.

Your receiver must answer with any 2xx status. Any other answer counts as a failure. See How notifications are sent.

The JSON document

Every notification is one POST with Content-Type: application/json and this body:

{
  "schema_version": 1,
  "notifier": "notify_ops",
  "id": "0b8f4c1e-4d3a-4f6f-8a57-2f3b1a9c5e01",
  "time": "2030-01-02T03:04:05Z",
  "severity": "critical",
  "code": "message_failed",
  "subject": {
    "kind": "message",
    "id": "7d4f5a52-6a3b-4d0e-9b53-0d6c5d1f9e11",
    "name": "acme to local"
  },
  "summary": "Message from acme to local failed",
  "details": [
    {
      "key": "from",
      "value": "acme"
    },
    {
      "key": "to",
      "value": "local"
    },
    {
      "key": "error",
      "value": "signature verification failed"
    }
  ],
  "dedupe_key": "message_failed:message:7d4f5a52-6a3b-4d0e-9b53-0d6c5d1f9e11",
  "link_path": "/ui/messages/7d4f5a52-6a3b-4d0e-9b53-0d6c5d1f9e11",
  "link": "https://microb2b.example.com/ui/messages/7d4f5a52-6a3b-4d0e-9b53-0d6c5d1f9e11"
}
PropertyWhat it holds
schema_versionThe version of this format, 1.
notifierThe name of the notifier that sent it.
testtrue for a test notification. Left out otherwise.
idThe alert’s unique ID.
timeWhen the alert was raised, in UTC.
severityinfo, warning or critical.
codeThe alert type, such as message_failed. See Notifications.
subjectWhat the alert is about: its kind, id and name.
summaryOne line that says what happened.
detailsA list of key and value pairs, in order.
dedupe_keyThe same for alerts about the same ongoing problem.
link_pathThe path of the page about the alert, when it has one.
linkThe full address of that page. Only present when the notifier has a Link Base URL.
groupOnly in a summary of held-back alerts. See below.

New properties may appear. schema_version changes only when a property is renamed, removed or gets another meaning. Ignore properties you do not know.

Treat the text as untrusted. A summary or detail can contain text from a partner, such as a file name.

Summaries of held-back alerts

When the rate limit holds alerts back, the notifier sends one summary at the end of the window. It has a group property:

{
  "schema_version": 1,
  "notifier": "notify_ops",
  "id": "2d0a6e30-6f5c-4b8b-8c79-4b5d3c1e7a23",
  "time": "2030-01-02T03:05:00Z",
  "severity": "critical",
  "code": "message_failed",
  "subject": {
    "kind": "plugin",
    "id": "notification-webhook/notify_ops",
    "name": "notify_ops"
  },
  "summary": "7 more alerts were held back by the rate limit",
  "details": [
    {
      "key": "Message failed",
      "value": "5"
    },
    {
      "key": "File unroutable",
      "value": "2"
    }
  ],
  "dedupe_key": "group:notify_ops:1893553500",
  "group": {
    "count": 7,
    "alerts": [
      {
        "id": "0b8f4c1e-4d3a-4f6f-8a57-2f3b1a9c5e01",
        "time": "2030-01-02T03:04:05Z",
        "severity": "critical",
        "code": "message_failed",
        "summary": "Message from acme to local failed",
        "link": "https://microb2b.example.com/ui/messages/7d4f5a52-6a3b-4d0e-9b53-0d6c5d1f9e11"
      },
      {
        "id": "1c9f5d2f-5e4b-4a7a-9b68-3a4c2b0d6f12",
        "time": "2030-01-02T03:04:06Z",
        "severity": "warning",
        "code": "file_unroutable",
        "summary": "File x.xml names no partner",
        "link": "https://microb2b.example.com/ui/messages"
      }
    ]
  }
}
  • group.count is the number of held-back alerts.
  • group.alerts lists the first 20 of them, each with id, time, severity, code, summary and, with a link base, link.
  • The summary’s own severity and code are those of the most urgent alert in it.
  • The details give a count per alert type.

Check the signature

With a Signing Secret, every request carries this header:

X-MicroB2B-Signature: t=<unix seconds>,v1=<hex>

v1 is the HMAC-SHA256 of the text <t>.<raw request body>, keyed with the signing secret.

Your receiver should:

  1. Read t and v1 from the header.
  2. Refuse the request if t is too old. Five minutes is a good limit. This stops someone from replaying an old request.
  3. Compute the HMAC-SHA256 of t, a dot and the raw body, with the secret.
  4. Compare it with v1 in constant time. Refuse the request if they differ.

In Python:

import hmac, hashlib, time

def verify(secret: bytes, header: str, body: bytes, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t, v1 = parts["t"], parts["v1"]
    if abs(time.time() - int(t)) > tolerance:
        return False
    expected = hmac.new(secret, t.encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Use the body exactly as received. Parsing and re-encoding the JSON changes the bytes, and the signature does not match.

Only Generic JSON is signed. The chat and push formats ignore the signing secret, because those services rely on the secret URL.

Other headers

You can add your own headers under Additional HTTP Headers, for example an Authorization header that your receiver checks. The values are write-only. See Notifications.

The notifier never follows a redirect. Point the URL at the final address.