Skip to content
POST /v1/eventsRequest access
API reference

Webhooks & alerts

Sieve pushes what matters to people (email, Slack) and to systems (signed webhooks).

decision.blockedAn event was blocked (verdict), with the reasons.
case.openedA case opened or was escalated.
case.closedA case was closed, with its outcome.
screening.matchA 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.

Node.js (Express)
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)
})
Python (Flask)
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.