Fraud Detection API Webhooks: Node.js Verification
Author: Yahor Papou, CMO at ShieldLabs. Updated on October 27, 2026. A fraud detection API webhook receiver in Node.js starts with the exact request bytes. Verify the signature before parsing JSON, store accepted busine
Author: Yahor Papou, CMO at ShieldLabs. Updated on October 27, 2026.
A fraud detection API webhook receiver in Node.js starts with the exact request bytes. Verify the signature before parsing JSON, store accepted business events durably, and make duplicate delivery harmless. A successful signature proves the body was signed with the endpoint secret; it does not make a downstream database write happen exactly once.
The public ShieldLabs webhook contract documents HMAC-SHA256 over the raw body and bounded, at-least-once delivery attempts. This guide separates that verification step from receipt, event parsing, and business processing so each failure has an explicit recovery path.
In 2026, we tested these helpers locally with synthetic fixtures: signature verification, malformed envelopes, duplicate identity, and UInt64 revision ordering. The fixture checks cover the code below; they do not measure live detection or production delivery.
Preserve the raw body
Read the request as bytes before any JSON middleware changes it. JSON can be equivalent after whitespace or key-order changes while the signed byte sequence is different. Pass the complete endpoint secret and the X-Shield-Signature header to the SDK verifier, then parse only after verification succeeds.
Install the public server SDK:
npm install @shieldlabs-ai/node
The following helper uses the SDK's signature verifier. It validates only the outer envelope needed for durable receipt. It is not a complete validator for every event-specific payload.
import { webhooks } from '@shieldlabs-ai/node';
export function verifyEnvelope(rawBody, signature, secret) {
if (!webhooks.verifySignature(rawBody, signature, secret)) {
throw new Error('Invalid webhook signature');
}
const event = JSON.parse(Buffer.from(rawBody).toString('utf8'));
if (!event || typeof event !== 'object' || Array.isArray(event)) {
throw new Error('Expected an event object');
}
if (event.event_type === 'webhook.ping') return { kind: 'ping', event };
if (!Number.isSafeInteger(event.site_id) || event.site_id <= 0 ||
typeof event.event_id !== 'string' || !event.event_id ||
typeof event.event_type !== 'string' ||
typeof event.schema_version !== 'string') {
throw new Error('Quarantine: incomplete event envelope');
}
return { kind: 'event', key: `${event.site_id}:${event.event_id}`, event };
}
The helper returns connectivity pings separately. For business events, an incomplete envelope is quarantined rather than silently assigned a made-up identifier. The public contract notes that older retained events may omit event_id; route those through an explicit legacy recovery process if you still consume them.
Return success after durable receipt
Insert the verified event into a persistent inbox before returning a successful HTTP response. Use (site_id, event_id) as the unique key for the modern envelope. If that key already exists, acknowledge the duplicate without creating another business action. If storage is unavailable, do not claim durable acceptance.
An illustrative inbox can contain these columns:
CREATE TABLE webhook_inbox (
site_id bigint NOT NULL,
event_id text NOT NULL,
event_type text NOT NULL,
schema_version text NOT NULL,
payload jsonb NOT NULL,
received_at timestamptz NOT NULL DEFAULT now(),
processed_at timestamptz,
PRIMARY KEY (site_id, event_id)
);
Keep access to stored payloads restricted. Retention and encryption are operational choices that should match the sensitivity of the event data. PostgreSQL documents the unique constraints behind the inbox's duplicate protection.
A worker can then select pending inbox rows, validate the event-specific schema, apply the action, and mark processing complete. Make its business writes idempotent too. Deduplicating receipt does not protect against a worker crash after it applied a side effect but before it recorded completion.
Use a fraud detection API as a documented recovery path
A fraud detection API and a webhook solve different delivery needs. The API can support an on-demand identification read when the documented endpoint permits it; the webhook carries an event to your receiver. A fraud prevention API integration should define which result is being recovered and keep bounded retry behavior, rather than treating an empty read as approval.
The public webhook reference describes a History fallback for identifications. It does not describe History as a replay API for group transitions. Keep those recovery paths separate and preserve the inbox record that explains why a worker could not complete its action.
Dispatch by event type and schema version
An identification result and a multi-account group transition are different contracts. The current scored-identification reference uses data.identification_id. The multi-account event reference describes group identity and revisions. Do not cast both into an old identification shape because they share a webhook endpoint.
Use an allowlist of supported (event_type, schema_version) pairs in the worker. Persist unknown versions for investigation, then hold their business actions. This gives you a recovery path when a contract changes without interpreting unfamiliar fields as an approval.
For group events, confirm availability for your site and endpoint configuration through the documented setup before depending on delivery. Enabling an endpoint does not imply replay of existing groups. Those are integration conditions, not assumptions the receiver should hide.
A signature does not provide an expiry window
The documented signature scheme signs the body without a separate signed delivery timestamp. A previously captured valid body can still verify. Event deduplication prevents the same logical event from applying again, while endpoint-secret protection, HTTPS, and restricted receiver access reduce exposure.
Do not describe a check against created_at as equivalent to a signed delivery-age guarantee. Legitimate deliveries can be delayed. If you enforce an age policy for business processing, define its recovery path and keep the verified receipt for investigation rather than losing it.
During rotation, the public Node SDK can verify against a list of acceptable secrets. Keep the overlap bounded operationally, then remove the old secret when the transition is complete. Never print signing secrets or entire customer payloads in routine error logs.
Test the failure paths before connecting a real action
Use a synthetic signed fixture to check valid delivery, an altered body, the wrong secret, malformed JSON, a duplicate event, and an unsupported schema version. Also exercise storage failure and a worker crash between its action and completion marker.
The verification and revision helpers accompanying this article were run locally with synthetic fixtures. Results: all 16 assertions passed, including body alteration, the wrong secret, missing event identity, and numeric revision ordering. That checks the code paths, not live delivery latency, detection accuracy, or your application's production storage guarantees. Test a real signed delivery in a separate environment before enabling irreversible actions.
For the account context behind these events, see the multi-accounting detection guide. ShieldLabs risk scoring and ready multi-accounting, account sharing, impossible travel, and account takeover detections connect through SDKs, API, and webhooks. Start Free to evaluate enterprise-level functionality without enterprise pricing.
Receiver questions
Can I verify a re-serialized JSON object?
No. Verify the exact received bytes. Parsing and serializing can change whitespace, escaping, or key order, producing a different HMAC input. Configure raw-body access before JSON middleware and use the original bytes for verification, even if the application later works with the parsed object.
Should duplicate deliveries return an error?
Not when the event is already durably accepted. A unique inbox key can identify that duplicate and let the receiver acknowledge it without repeating the action. Storage failures and invalid signatures are different cases. Distinguish them so retries recover missing work instead of creating additional side effects.
Is an event ID enough to order updates?
An event ID identifies a logical event; it is not a sortable revision. Use the ordering field defined by the event-specific contract. For multi-account groups, the documented key includes site, publication epoch, and cluster ID, with numeric revision comparison. Do not overwrite newer state based on arrival order.
References
[1] ShieldLabs: multi accounting detection
[2] ShieldLabs: risk scoring
[3] docs.shieldlabs.ai: webhooks
[4] www.postgresql.org: ddl constraints.html
[5] docs.shieldlabs.ai: webhook identification
[6] docs.shieldlabs.ai: webhook multi account
Questions or corrections: [email protected].
Summarize this article with
ChatGPT Β· Claude Β· Grok Β· Grok on X Β· Perplexity Β· Google AI
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.