Webhooks & alerts
Sieve pushes what matters to people (email, Slack) and to systems (signed webhooks).
decision.blocked | An event was blocked (verdict), with the reasons. |
case.opened | A case opened or was escalated. |
case.closed | A case was closed, with its outcome. |
screening.match | A customer resembles someone on a sanctions or PEP list. |
Verify the signature
Every delivery carries Sieve-Signature: t=<unix>,v1=<hex>, an HMAC-SHA256 of t + "." + body with your endpoint’s secret. Reject anything unsigned or older than five minutes.
import express from 'express' import { verifyWebhook } from '@sievefraud/node' const app = express() // Keep the raw body: the signature is over the exact bytes Sieve sent. app.post('/webhooks/sieve', express.raw({ type: 'application/json' }), (req, res) => { const ok = verifyWebhook(req.body, req.get('Sieve-Signature') ?? '', process.env.SIEVE_WEBHOOK_SECRET) if (!ok) return res.sendStatus(400) // unsigned, tampered with, or older than five minutes const event = JSON.parse(req.body) if (event.type === 'decision.blocked') notifyOpsTeam(event.data) res.sendStatus(200) })
import hmac, hashlib, time, os from flask import Flask, request, abort app = Flask(__name__) @app.post("/webhooks/sieve") def sieve_webhook(): parts = dict(p.split("=", 1) for p in request.headers.get("Sieve-Signature", "").split(",")) body = request.get_data() # raw bytes expected = hmac.new(os.environ["SIEVE_WEBHOOK_SECRET"].encode(), f"{parts.get('t')}.".encode() + body, hashlib.sha256).hexdigest() if abs(time.time() - int(parts.get("t", 0))) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")): abort(400) event = request.get_json() return "", 200
Deliveries that fail are retried with backoff for about six hours. Every attempt is listed in Developers → Webhooks.