Node.js SDK
@sievefraud/node is the official client for Node.js 18 and later. It ships with TypeScript types and has no dependencies.
npm install @sievefraud/node
Create a client
Create one client and reuse it. Keys start with sv_test_ or sv_live_. Keys issued before the rename start with snr_ and still work.
import { Sieve } from '@sievefraud/node' // One client per process. Keep the key on your server, never in an app or website. export const sieve = new Sieve(process.env.SIEVE_KEY, { timeout: 5000, // ms per attempt (default 5000) maxRetries: 2, // on network errors, 429 and 5xx (default 2) // baseUrl: 'https://sieve.yourbank.local', // only if you run Sieve on your own servers })
| Option | Default | Description |
|---|---|---|
timeout | 5000 | Milliseconds per attempt. A fraud check should never hang a payout. |
maxRetries | 2 | Retries after a network error, a 429 or a 5xx, with backoff. Retry-After is honoured. |
baseUrl | https://sievefraud.com | Change it only if Sieve runs on your own servers. |
Methods
| Method | Calls | Returns |
|---|---|---|
sieve.events.create(event) | POST /v1/events | The decision. See Events reference and Decisions & reasons. |
sieve.customers.get(id) | GET /v1/customers/:id | The customer’s status, risk level, screening result and open cases. |
sieve.feedback(input) | POST /v1/feedback | Tells Sieve whether an event was fraud or legitimate. |
sieve.screen(input) | POST /v1/screen | Sanctions and PEP matches for a name, without creating a customer. |
verifyWebhook(body, header, secret) | Nothing: runs locally | true if a webhook delivery was signed by Sieve and is less than five minutes old. |
// Check a beneficiary before you add them, without creating a customer. const { match, results } = await sieve.screen({ name: 'Jane Wanjiru', dob: '1984-03-12', nationality: 'KE' })
Retries and idempotency
Every events.create call carries an idempotency key, so the SDK’s own retries never count an event twice. Pass your own idempotency_key to make your retries safe too.
// Your own payout ID makes your own retries safe too: the same key returns the first decision. const d = await sieve.events.create({ type: 'payout.requested', customer_id: user.id, amount: 48000, destination: { type: 'mpesa', account: payout.phone }, idempotency_key: `payout_${payout.id}`, })
Errors
A failed call throws a SieveError with status, code, message and details. Decide what your app does when Sieve can’t be reached. For payouts, holding the money is the safe choice.
import { SieveError } from '@sievefraud/node' try { const d = await sieve.events.create(event) if (d.decision !== 'allow') return holdPayout(payout.id, d.reasons) } catch (err) { if (!(err instanceof SieveError)) throw err // err.status: 400, 401, 429 … or 0 if Sieve couldn't be reached after retries // err.code: 'invalid_request', 'invalid_api_key', 'rate_limited', 'network_error' … // err.details: which fields were wrong, for 400s return holdPayout(payout.id, [{ reason: 'Fraud check unavailable' }]) // fail safe: hold, don't pay }
Webhooks
Pass the raw request body, not parsed JSON: the signature covers the exact bytes Sieve sent. The events are listed in Webhooks & alerts.
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) })
TypeScript
Types ship with the package, including SieveEvent, Decision, Verdict, SieveError and SieveOptions. The old Snare names still work but are deprecated.