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
- Go to Plugins > Webhook Notifiers and click + New Webhook Notifier.
- Choose the format Generic JSON.
- Enter your receiver’s address as the Webhook URL.
- Enter a Signing Secret: a long random value that your receiver also knows. For example, make one with
openssl rand -hex 32. - Click Create and restart MicroB2B.
- 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"
}| Property | What it holds |
|---|---|
schema_version | The version of this format, 1. |
notifier | The name of the notifier that sent it. |
test | true for a test notification. Left out otherwise. |
id | The alert’s unique ID. |
time | When the alert was raised, in UTC. |
severity | info, warning or critical. |
code | The alert type, such as message_failed. See Notifications. |
subject | What the alert is about: its kind, id and name. |
summary | One line that says what happened. |
details | A list of key and value pairs, in order. |
dedupe_key | The same for alerts about the same ongoing problem. |
link_path | The path of the page about the alert, when it has one. |
link | The full address of that page. Only present when the notifier has a Link Base URL. |
group | Only 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.countis the number of held-back alerts.group.alertslists the first 20 of them, each withid,time,severity,code,summaryand, with a link base,link.- The summary’s own
severityandcodeare those of the most urgent alert in it. - The
detailsgive 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:
- Read
tandv1from the header. - Refuse the request if
tis too old. Five minutes is a good limit. This stops someone from replaying an old request. - Compute the HMAC-SHA256 of
t, a dot and the raw body, with the secret. - Compare it with
v1in 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.