Tutorials
Get notified with webhooks
Instead of checking a job again and again, let RasterNivo send your server a message when it ends. Your server needs a public HTTPS address.
1. Add a receiver to your server
This code checks the signature, then handles the event. Set RASTERNIVO_WEBHOOK_SECRET in the next step.
<?php
// webhook.php: point your webhook endpoint at this file.
$secret = getenv('RASTERNIVO_WEBHOOK_SECRET'); // whsec_...
$body = file_get_contents('php://input'); // the raw body, before any JSON parsing
$timestamp = 0;
$signatures = [];
foreach (explode(',', $_SERVER['HTTP_RASTERNIVO_SIGNATURE'] ?? '') as $part) {
[$name, $value] = array_pad(explode('=', $part, 2), 2, '');
if ($name === 't') $timestamp = (int) $value;
if ($name === 'v1') $signatures[] = $value;
}
$expected = hash_hmac('sha256', "$timestamp.$body", $secret);
$valid = false;
foreach ($signatures as $signature) {
$valid = $valid || hash_equals($expected, $signature);
}
if (!$valid || abs(time() - $timestamp) > 300) { // also rejects replays older than 5 minutes
http_response_code(400);
exit;
}
$event = json_decode($body, true);
// De-duplicate with $_SERVER['HTTP_RASTERNIVO_EVENT_ID'], then do your work.
error_log($event['type'] . ' ' . $event['data']['conversion']['id']);
http_response_code(200);# webhook.py: a small receiver using only the standard library.
import hashlib
import hmac
import json
import os
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET = os.environ["RASTERNIVO_WEBHOOK_SECRET"] # whsec_...
def is_valid(body: bytes, header: str) -> bool:
parts = [p.split("=", 1) for p in header.split(",") if "=" in p]
timestamp = next((v for k, v in parts if k == "t"), "0")
if abs(time.time() - int(timestamp)) > 300: # reject replays older than 5 minutes
return False
expected = hmac.new(SECRET.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
return any(k == "v1" and hmac.compare_digest(expected, v) for k, v in parts)
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers.get("Content-Length", 0))) # raw body
if not is_valid(body, self.headers.get("RasterNivo-Signature", "")):
self.send_response(400)
self.end_headers()
return
event = json.loads(body)
# De-duplicate with the RasterNivo-Event-Id header, then do your work.
print(event["type"], event["data"]["conversion"]["id"])
self.send_response(200)
self.end_headers()
HTTPServer(("", int(os.environ.get("PORT", 3000))), Handler).serve_forever()// webhook.mjs: a small receiver using only built-in modules.
import { createHmac, timingSafeEqual } from "node:crypto";
import { createServer } from "node:http";
const secret = process.env.RASTERNIVO_WEBHOOK_SECRET; // whsec_...
function isValid(body, header = "") {
const parts = header.split(",").map((p) => p.split("="));
const timestamp = parts.find(([k]) => k === "t")?.[1] ?? "0";
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; // reject old replays
const expected = Buffer.from(createHmac("sha256", secret).update(`${timestamp}.`).update(body).digest("hex"));
return parts.some(([k, v]) => k === "v1" && v.length === expected.length && timingSafeEqual(Buffer.from(v), expected));
}
createServer((req, res) => {
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => {
const body = Buffer.concat(chunks); // the raw body, before any JSON parsing
if (!isValid(body, req.headers["rasternivo-signature"])) {
res.writeHead(400).end();
return;
}
const event = JSON.parse(body);
// De-duplicate with the rasternivo-event-id header, then do your work.
console.log(event.type, event.data.conversion.id);
res.writeHead(200).end();
});
}).listen(process.env.PORT ?? 3000);2. Register your address
- Open Webhooks in your dashboard.
- Enter your HTTPS address and choose the events you want.
- Click Add endpoint, then copy the signing secret, which is shown only once, into
RASTERNIVO_WEBHOOK_SECRET.
3. Test it
Click Send test next to your endpoint. Deliveries shows what was sent and what your server answered. If it failed, RasterNivo tries again later.