Overview
Every production webhook receiver should verify the PulseGrid signature before processing the JSON.
SDK 0.2.0 includes verification helpers in both official SDKs, so application code does not need to reimplement HMAC logic for every framework.
Critical rule: verify the original raw request body. Parsing JSON and serializing it again can change whitespace/key order and invalidate a correct signature.
Setup
Node.js / Express:
1. Store PULSEGRID_WEBHOOK_SECRET on the server.
2. Configure the webhook route with express.raw({ type: 'application/json' }).
3. Pass req.headers and req.body to constructWebhookEvent.
4. Process event.payload only after verification succeeds.
Python / Django:
1. Store PULSEGRID_WEBHOOK_SECRET on the server.
2. Pass request.headers and request.body to construct_webhook_event.
3. CSRF-exempt the external webhook route when appropriate.
4. Process event['payload'] only after verification succeeds.
The helpers reject invalid/expired signatures using the timestamp tolerance.
Code example
import express from "express";
import { constructWebhookEvent } from "pulsegrid/webhooks";
const app = express();
app.post(
"/webhooks/pulsegrid",
express.raw({ type: "application/json" }),
async (req, res) => {
try {
const event = constructWebhookEvent({
secret: process.env.PULSEGRID_WEBHOOK_SECRET,
headers: req.headers,
body: req.body,
});
await processPulseGridEvent(event.payload);
return res.status(200).json({ received: true });
} catch (error) {
return res.status(401).json({ error: error.code || "invalid_webhook" });
}
}
);
import os
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from pulsegrid import construct_webhook_event
@csrf_exempt
@require_POST
def pulsegrid_webhook(request):
try:
event = construct_webhook_event(
secret=os.environ["PULSEGRID_WEBHOOK_SECRET"],
headers=request.headers,
body=request.body,
)
except Exception as exc:
return JsonResponse({"error": getattr(exc, "code", "invalid_webhook")}, status=401)
process_pulsegrid_event(event["payload"])
return JsonResponse({"received": True}, status=200)
Notes
Do not log or return the signing secret. Rotate it if exposed.
If you use another language without an official SDK, implement the documented timestamp + '.' + raw body HMAC-SHA256 algorithm with constant-time comparison and replay-age validation.