GuidesConfiguring webhooks
Validating webhook payloads
Check the X-Mothership-Signature header with your webhook configuration's secret key.
The Create a webhook configuration and Retrieve a webhook configuration responses each contain a secretKey property that can be used to validate the signature of incoming Mothership webhook events.
Secret keys are unique to webhook configs.
{
"data": {
"id": "48asjgaoi33nkl4t3lae",
"createdAt": "2019-03-20T16:17:13.424Z",
"status": "active",
"subscribedEvents": ["shipment.purchased", "billOfLading.created", "proofOfDelivery.created"],
"webhookUrl": "https://www.yourserver.io/webhook/mothership",
"secretKey": "de3ADDAek3245DLAdANe91Dn3asklAMSsna03aGKkl23agsJea"
}
}The X-Mothership-Signature header is the hex-encoded HMAC-SHA256 of the raw request body, keyed with the secret key. To validate a payload, compute the same HMAC over the body exactly as you received it and compare it to the header.
Let's validate a webhook payload. Both examples read the secret key from the MOTHERSHIP_WEBHOOK_SECRET environment variable and use a constant-time comparison.
import crypto from 'node:crypto'
import express from 'express'
const app = express()
// Check the signature against the raw body, before any JSON parsing.
const rawBody = express.raw({ type: 'application/json' })
app.post('/webhooks/mothership', rawBody, (req, res) => {
const expected = crypto
.createHmac('sha256', process.env.MOTHERSHIP_WEBHOOK_SECRET)
.update(req.body)
.digest('hex')
const received = req.get('X-Mothership-Signature') ?? ''
if (
!/^[a-f0-9]{64}$/.test(received) ||
!crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
) {
return res.sendStatus(401)
}
const event = JSON.parse(req.body.toString('utf8'))
// event.eventType, event.data, event.sentAt, event.deliveryAttempt, event.pendingRetries
res.sendStatus(200)
})import hashlib
import hmac
import os
import re
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["MOTHERSHIP_WEBHOOK_SECRET"].encode()
@app.post("/webhooks/mothership")
def mothership_webhook():
# Check the signature against the raw body, before any JSON parsing.
expected = hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
received = request.headers.get("X-Mothership-Signature", "")
if not re.fullmatch(r"[a-f0-9]{64}", received) or not hmac.compare_digest(received, expected):
abort(401)
event = request.get_json()
# event["eventType"], event["data"], event["sentAt"], event["deliveryAttempt"], event["pendingRetries"]
return "", 200Check a signature in your browser
Paste a delivery's raw body and your secret key to see the X-Mothership-Signature value Mothership sends. The key and body stay on this page; the HMAC runs in your browser with Web Crypto.
X-Mothership-Signature
Signing…Why the raw body matters
Any change to the bytes, even whitespace or key order, gives a different signature. Verify before you parse.