@protegeyinc/sdk-node
v0.2.1
Published
Node.js SDK for Protegey signal ingestion
Readme
@protegeyinc/sdk-node
Node.js SDK for Protegey — continental fraud intelligence for Africa.
Handles signal ingestion, outcome reporting, UFES v2 normalization, validation, batching, and retry — so your integration stays clean.
Requirements
- Node.js >= 17.3.0
- A Protegey API key (
cen_live_sk_...orcen_sandbox_sk_...)
Installation
npm install @protegeyinc/sdk-node
# or
pnpm add @protegeyinc/sdk-nodeQuick start — Simple API
Best for server-side apps where a single identity is active per request context.
import { protegey } from '@protegeyinc/sdk-node'
// Once at startup
protegey.init({ apiKey: 'cen_live_sk_...' })
// On each request — set the actor
protegey.identify('user_hashed_id', 'individual', {
phone: '+233241234567',
})
// Track an event
const result = await protegey.track('LOGIN_FAILURE', {
auth_method: 'otp',
failure_reason: 'wrong_pin',
})
console.log(result.data.risk_level) // 'low' | 'medium' | 'high' | 'critical' | 'minimal'Advanced API — ProtegeyClient
Use when you need per-request identity control, multiple tenants, or custom batching.
import { ProtegeyClient } from '@protegeyinc/sdk-node'
const client = new ProtegeyClient({
apiKey: 'cen_live_sk_...',
maxRetries: 3,
batchSize: 10,
flushIntervalMs: 2000,
onWarning: (w) => console.warn('[protegey]', w.message),
})
const result = await client.track(
'PAYMENT',
{ amount: 500, currency: 'GHS' },
{ phone: '+233241234567' },
{
eventCategory: 'transaction',
sessionContext: {
session_id: 'sess_abc123xyz',
sequence: 3,
},
},
)
// Report outcome once the transaction resolves
await client.outcome(result.data.signal_id, {
fraud_outcome: 'legitimate',
event_outcome: 'success',
})
// Flush any buffered signals before process exit
await client.shutdown()Configuration
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | required | Your Protegey API key |
| baseUrl | string | production | Override for sandbox or self-hosted |
| timeoutMs | number | 10000 | Per-request timeout in ms |
| maxRetries | number | 2 | Retry attempts on 5xx / network errors |
| idempotency | boolean | true | Attach Idempotency-Key header on retries |
| batchSize | number | 10 | Flush when queue reaches this size |
| flushIntervalMs | number | 2000 | Auto-flush interval in ms (0 = disabled) |
| flushConcurrency | number | 5 | Max concurrent requests per flush |
| maxPayloadBytes | number | 524288 | Max signal size in bytes (512 KB) |
| partnerUserId | string | — | Partner-side user ID forwarded in source |
| integrationChannel | 'api' \| 'batch' \| 'stream' | 'api' | How signals are submitted |
| sourceIpAddress | string | — | Originating IP forwarded in source |
| sourceConfidenceScore | number | — | 0–1 confidence in the source data |
| onWarning | (w: SdkWarning) => void | — | Called when unknown fields are pruned |
Tracking events
track(eventType, payload?, identify?, options?)
await client.track(
'ACCOUNT_TAKEOVER',
{ method: 'sim_swap' },
{
phone: '+234801234567',
deviceIntelligence: {
is_emulator: false,
sdk_integrity: 'valid',
},
},
{
eventCategory: 'auth',
traceId: 'trace_xyz',
sessionContext: {
session_id: 'sess_abc123xyz789',
sequence: 1,
},
relatedEntities: [
{
entity_id: 'acct_hashed_receiver',
relationship_type: 'receiver',
source: 'declared',
},
],
},
)Outcome reporting
Call outcome() once you know the real-world result of an event:
await client.outcome(signalId, {
fraud_outcome: 'fraud', // 'fraud' | 'legitimate' | 'suspected' | 'unknown'
fraud_type: 'scam', // 'account_takeover' | 'scam' | 'laundering' | 'mule' | 'unknown'
event_outcome: 'blocked', // 'success' | 'failure' | 'blocked' | 'reversed'
event_outcome_source: 'partner',
outcome_timestamp: new Date(), // Date objects are coerced to ISO strings
})Sandbox
protegey.init({
apiKey: 'cen_sandbox_sk_...',
baseUrl: 'https://api.protegey.com/api/v1',
})Or against a local backend:
PROTEGEY_BASE_URL=http://localhost:8000/api/v1 node your-script.jsValidation
The SDK validates every signal against the UFES v2 JSON schema before sending. Unknown fields are pruned and surfaced via onWarning rather than causing a hard failure — this keeps integrations compatible across SDK versions.
const client = new ProtegeyClient({
apiKey: '...',
onWarning: ({ path, message }) => {
// e.g. path: "primary_actor.legacy_field", code: "unknown_field_pruned"
logger.warn('Protegey SDK pruned unknown field', { path, message })
},
})A UfesValidationError is thrown (not retried) when:
- A required field is missing
- An enum value is invalid
- The signal exceeds
maxPayloadBytes
Error handling
import { ProtegeyRequestError, UfesValidationError } from '@protegeyinc/sdk-node'
try {
await client.track('PAYMENT', payload, identity)
} catch (e) {
if (e instanceof UfesValidationError) {
// Schema problem — fix the payload, don't retry
console.error('Validation failed:', e.details)
} else if (e instanceof ProtegeyRequestError) {
// HTTP error after all retries exhausted
console.error(`HTTP ${e.statusCode}:`, e.body)
}
}Lifecycle
// Flush all buffered signals and reject new track() calls
await client.shutdown()
// Or flush without shutting down (e.g. end of a batch job tick)
await client.flush()Smoke test
Run against a live backend to verify end-to-end connectivity:
PROTEGEY_API_KEY=cen_sandbox_sk_... npx tsx scripts/smoke.ts