Webhooks
This guide explains how to set up and use SEON Webhooks for real-time event notifications. It includes setup instructions, payload references, retry policies, troubleshooting, and best practices.
Introduction
SEON Webhooks let you receive real-time notifications when important events occur in your SEON environment:
- A transaction is processed
- Transaction status changes
- List updates (blacklist, whitelist, custom lists)
- AML alerts (person & entity updates)
- Identity verification (session completed, status updates)
- An eKYC process finished
When any of these events occur, SEON sends an HTTP POST request with JSON payload to the configured webhook URL.
Key features
- Multiple endpoints: 10 webhook endpoints per client; create/edit/delete from the UI.
- Monitoring dashboard: Real-time log with event type, timestamp, endpoint, status; expandable request/response details; filters by event type, endpoint, status, time range. Logs are retained for 30 days.
- Secure delivery: All payloads are signed with HMAC-SHA256.
- Reliable delivery: Guaranteed at-least-once delivery with retries.
- Idempotent events: Each event has a
Request-Idheader.
How to register a new webhook
To register a webhook, go to Settings→System→Webhooks.

- Click Create new.
- Enter name and URL.
- Select the event types
- Validation step to check that the URL works - you need to send
aHTTP 200response code in order to save the webhook. The endpoint should be able to handle the ping check and return a successful response. This request is not signed with a signature. - In case of a successful validation the webhook can be saved
- From that point on, you can see deliveries for that specific URL
Ping check:
{
"event": "ping",
"message": "Webhook URL validation check."
}Reference implementation:
import { Request, Response } from "express";
function verifyWebhookSignature(
rawPayload: string,
signatureHeader: string,
secret: string
): boolean {
// existing implementation
return true;
}
export function webhookHandler(req: Request, res: Response) {
const secret = process.env.WEBHOOK_SECRET;
// Get signature from SEON-Signature header
const signatureHeader = req.header("SEON-Signature");
const payload = JSON.parse(req.body);
// 1) ping event → no signature verification
if (payload.event === "ping") {
return res.status(200).json({ ok: true, event: "pong" });
}
// 2) Other events → require and verify signature
const isValid = verifyWebhookSignature(req.body.toString(), signatureHeader, secret);
if (!isValid) {
return res.status(403).json({ ok: false, error: "Invalid signature" });
}
// 3) Process the event here...
return res.status(200).json({ ok: true });
}Logs
The logs page shows every single delivery attempt of SEON's webhook service. Every single item on this page is an attempt to deliver a registered webhook event. If one event failed and at a later point had any number of retries, then for that ID you will be able to see every single retry attempt as an individual record on the logs page. You can see the request and the response for every single delivery attempt.
Testing webhook payloads
On the test tab, you can test every event payload. You simply provide a webhook URL and select the event you would like to see the test payload for, and we will send a test attempt. If you can see and monitor that webhook URL, you will be able to see instantly what future event structures will look like.
Webhook headers
Every webhook request includes these headers:
Request-Id: Unique per event — use it to prevent duplicate processing.Content-Type: Always application/json.User-Agent: seon/<version>.Webhook-Schema-Version: Schema version (e.g., v1).SEON-Signature: Includes timestamp, key ID, and HMAC signature.
Signature verification
Verify every webhook signature before processing:
import crypto from "node:crypto"
function verifyWebhookSignature(
rawPayload: string,
signatureHeader: string,
secret: string
): boolean {
// Parse the signature header
const parts: Record<string, string> = {};
const signatures: string[] = [];
signatureHeader.split(',').forEach(part => {
const [key, value] = part.split('=').map(s => s.trim());
if (key === 'sig') {
signatures.push(value);
} else {
parts[key] = value;
}
});
const timestamp = parts.t;
if (!timestamp || signatures.length === 0) {
return false;
}
// Validate timestamp (optional but recommended)
const MAX_AGE_SECONDS = 300; // 5 minutes
const now = Math.floor(Date.now() / 1000);
if (now - parseInt(timestamp) > MAX_AGE_SECONDS) {
console.warn('Webhook timestamp too old');
return false;
}
// Reconstruct the signed string
const signedString = `${timestamp}.${rawPayload}`;
// Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedString)
.digest('hex');
// Try to verify against each signature (supports key rotation)
for (const signature of signatures) {
try {
if (crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(signature)
)) {
return true;
}
} catch (error) {
// Length mismatch or other error, try next signature
continue;
}
}
return false;
}Retry Policy
SEON guarantees at-least-once delivery via automatic retries.
| retryCount | Delay (seconds) | Delay (hh:mm:ss) |
|---|---|---|
| 0 | 60 | 00:01:00 |
| 1 | 120 | 00:02:00 |
| 2 | 240 | 00:04:00 |
| 3 | 480 | 00:08:00 |
| 4 | 960 | 00:16:00 |
| 5 | 1,920 | 00:32:00 |
| 6 | 3,840 | 01:04:00 |
| 7 | 7,680 | 02:08:00 |
| 8 | 15,360 | 04:16:00 |
Retry Conditions
- No retry: HTTP 200, 201, 202, 204, 4xx except 429
- Retry: HTTP 429, 5xx, connection timeouts, network errors.
Troubleshooting
- Signature verification failing? Check:
- Correct signing key.
- Timestamp tolerance.
- Unmodified request body.
- Character encoding.
- Duplicate webhooks? Use
Request-Idto deduplicate. - Not receiving events? Ensure your endpoint:
- Is public and accessible.
- Responds with valid status codes.
- Supports HTTPS with SSL.
- Accepts POST requests.
Best Practices
- Always verify webhook signatures before processing.
- Reply fast, do the processing later.
- Implement idempotency using Request-Id headers.
- Respond within 10 seconds to avoid retries.
- Use HTTPS with valid SSL.
- Log webhook events for debugging and monitoring.
Webhook events
Transaction status updates
Sent when a transaction’s status changes (e.g. moved to review, approved, or declined).
{
"timestamp": "2017-08-30T13:47:42+00:00",
"event": "transaction/status_update",
"data": {
"id": "e601f2dae8f9",
"state": "REVIEW",
"label": "Marked as review"
},
}Transaction processed
Triggered when a transaction is processed in SEON. The payload contains the complete Fraud API response, including scores, signals, and decision data.
Response
Blacklist / Whitelist updates
Triggered when a user or data point is added to or removed from a blacklist or whitelist. Sent when a transaction’s status changes (e.g. moved to review, approved, or declined).
Response
Custom list updates
Fired when a record is updated in a custom list, such as a watchlist or internal tracking list.
Response
AML Person updates
Notifies you when AML screening results for a person change (e.g. new PEP or sanctions hit).
Response
AML Entity Updates
Notifies you of AML status changes for entities such as companies or organizations.
Response
Identity Verification – session finished
Indicates that an identity verification session has been completed and a final decision is available.
Response
Identity Verification – status updated
Sent when an ongoing IDV session’s status changes (e.g. moved to review or pending).
Response
eKYC finished
Triggered when an eKYC verification flow has completed, providing the outcome and verification details.