Skip to content
POST /v1/eventsRequest access
Get started

Node.js SDK

@sievefraud/node is the official client for Node.js 18 and later. It ships with TypeScript types and has no dependencies.

Terminal
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.

sieve.js
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
})
OptionDefaultDescription
timeout5000Milliseconds per attempt. A fraud check should never hang a payout.
maxRetries2Retries after a network error, a 429 or a 5xx, with backoff. Retry-After is honoured.
baseUrlhttps://sievefraud.comChange it only if Sieve runs on your own servers.

Methods

MethodCallsReturns
sieve.events.create(event)POST /v1/eventsThe decision. See Events reference and Decisions & reasons.
sieve.customers.get(id)GET /v1/customers/:idThe customer’s status, risk level, screening result and open cases.
sieve.feedback(input)POST /v1/feedbackTells Sieve whether an event was fraud or legitimate.
sieve.screen(input)POST /v1/screenSanctions and PEP matches for a name, without creating a customer.
verifyWebhook(body, header, secret)Nothing: runs locallytrue if a webhook delivery was signed by Sieve and is less than five minutes old.
Screen a beneficiary
// 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.

Idempotency
// 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.

Handle errors
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.

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)
})

TypeScript

Types ship with the package, including SieveEvent, Decision, Verdict, SieveError and SieveOptions. The old Snare names still work but are deprecated.