Getting Started
ReceiptorX lets you verify Nigerian bank receipts using AI — detecting fraud, edited fields, and fake screenshots in milliseconds. Here's how to go from zero to your first verification.
rcx_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6
POST to /api/verify with the receipt image and the payment details you expect to see on it.curl -X POST https://receiptor-x.onrender.com/api/verify \ -H "X-Api-Key: rcx_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "image": "data:image/jpeg;base64,...", "config": { "expectedRecipientName": "Ayo David", "expectedBankName": "GTBank", "expectedAmount": 5000, "expectedAccountNumber": "0123456789" } }'
valid boolean, a human-readable reason, and the detected fields the AI actually extracted from the image.{ "valid": true, // ← the only thing you need to check "reason": "All checks passed.", "detected": { "bank": "GTBank", "amount": 5000, "accountNumber": "0123456789", "name": "Ayo David", "date": "2026-06-27" } }
Authentication
The verify endpoint uses API key authentication via the X-Api-Key header. The dashboard and SSE stream use JWT Bearer tokens.
POST /api/verify. Pass your key in the X-Api-Key header.X-Api-Key: rcx_live_your_key_here
Authorization: Bearer <jwt>
rcx_live_ key in client-side JavaScript, a Git repository, or a public URL. Store it as an environment variable on your server.Verify Receipt
The main endpoint. Submit a receipt image with the payment details you expect — the AI checks every field and returns a definitive result with an explanation.
/api/verify
Auth: X-Api-Key
What the AI checks
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
image | string | required | Base64 data URL (data:image/jpeg;base64,…) or public HTTPS image URL |
config.expectedRecipientName | string | required | Name as it should appear on the receipt |
config.expectedBankName | string | required | Bank to verify — e.g. GTBank, OPay, Kuda |
config.expectedAmount | number | required | Exact amount as a number (no currency symbol) |
config.expectedAccountNumber | string | recommended | 10-digit NUBAN account number. Strongly recommended — edited account digits are the #1 fraud vector in Nigerian scams |
config.currency | string | optional | Currency code — default NGN |
config.allowedNameVariations | string[] | optional | Acceptable name alternatives, e.g. ["A. David", "Ayo D."] |
config.maxDateAgeDays | number | optional | Max receipt age in days — default 1 |
config.timezone | string | optional | IANA timezone — default Africa/Lagos |
Code examples
Response — valid receipt
{ "valid": true, "core_valid": true, "date_valid": true, "reason": "All checks passed.", "requestId": "8f3c2a1b-4d9e-…", "detected": { "bank": "GTBank", "amount": 5000, "name": "Ayo David", "accountNumber": "0123456789", "date": "2026-06-27", "transactionId": "220626…" } }
Response — account number fraud detected
{ "valid": false, "core_valid": false, "date_valid": true, "reason": "Account number on receipt (0123456788) does not match expected (0123456789). Last digit appears digitally altered.", "requestId": "4d9a7e2f-…", "detected": { "bank": "GTBank", "amount": 5000, "accountNumber": "0123456788", // what the AI actually saw "date": "2026-06-27" } }
Live Events (SSE)
Subscribe to a real-time stream of verification events for your dashboard. Every new result is pushed to you instantly — no polling, no delays.
/api/events/stream
Auth: ?token=<jwt>
EventSource API doesn't support custom headers. Pass your JWT as a ?token= query parameter — the server handles it securely server-side.const source = new EventSource( `https://receiptor-x.onrender.com/api/events/stream?token=${jwtToken}` ); source.addEventListener('connected', () => { console.log('🟢 Stream connected'); }); source.addEventListener('verification', e => { const event = JSON.parse(e.data); // event = { valid, bank, amount, accountNumber, reason, requestId, createdAt } if (event.valid) { console.log('✓ Valid', event.bank, 'NGN', event.amount); } else { console.warn('✗ Flagged:', event.reason); } }); source.onerror = () => source.close(); // reconnect as needed
Event payload fields
| Field | Type | Description |
|---|---|---|
valid | boolean | Overall result |
bank | string|null | Detected bank name |
amount | number|null | Detected amount |
accountNumber | string|null | Detected NUBAN account number |
reason | string | Human-readable explanation |
requestId | string | UUID — matches the requestId in your audit log |
createdAt | string | ISO 8601 timestamp |
Webhooks
Webhooks are HTTP POST requests that ReceiptorX sends to your server every time a verification completes. Unlike SSE, they work server-to-server without a persistent connection.
Events
| Event | Fired when |
|---|---|
verification.complete | Receipt passed all checks (valid: true) |
verification.failed | Receipt failed any check (valid: false) |
Payload
{ "event": "verification.complete", "valid": true, "bank": "GTBank", "amount": 5000, "accountNumber": "0123456789", "reason": "All checks passed.", "requestId": "8f3c2a1b-…", "createdAt": "2026-06-27T12:00:00.000Z" }
Verifying the signature
Every request includes an X-ReceiptorX-Signature header. Verify it on your server to confirm the request came from ReceiptorX and wasn't tampered with.
import { createHmac, timingSafeEqual } from 'crypto'; function verifySignature(rawBody, signature, secret) { const expected = 'sha256=' + createHmac('sha256', secret) .update(rawBody) .digest('hex'); const a = Buffer.from(expected); const b = Buffer.from(signature); if (a.length !== b.length) return false; return timingSafeEqual(a, b); // timing-safe compare } // In your Express route: app.post('/webhooks/rcx', express.raw({ type: '*/*' }), (req, res) => { const sig = req.headers['x-receiptorx-signature']; if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body); // handle event.valid, event.bank, etc. res.sendStatus(200); });
200 as fast as possible and process the event asynchronously.Error Codes
All errors include a message field and an X-Request-ID header for tracing.
| Status | Meaning | What to do |
|---|---|---|
200 | OK | Check valid field — the request succeeded even if the receipt is invalid |
400 | Bad Request | Fix missing or invalid fields in your request body |
401 | Unauthorized | Check your API key — wrong format, missing, or expired |
403 | Forbidden | Key has been revoked — generate a new one |
429 | Rate Limited | Check Retry-After header and slow down |
502 | AI Engine Error | The AI model failed — retry with exponential backoff |
500 | Server Error | Retry; report persistent issues with the X-Request-ID |
Rate Limits
Limits are applied per API key. Check the response headers to see how many requests you have left.
HTTP/1.1 429 Too Many Requests Retry-After: 42 RateLimit-Limit: 30 RateLimit-Remaining: 0 RateLimit-Reset: 1751026800
SDK & Libraries
Drop the zero-dependency client into your project, or use any HTTP library directly.
JavaScript / TypeScript (zero dependencies)
export class ReceiptorX { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = 'https://receiptor-x.onrender.com'; } async verify(image, config) { const res = await fetch(`${this.baseUrl}/api/verify`, { method: 'POST', headers: { 'X-Api-Key': this.apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ image, config }), }); if (!res.ok) throw new Error(`ReceiptorX ${res.status}: ${(await res.json()).message}`); return res.json(); } } // Usage: const rcx = new ReceiptorX('rcx_live_…'); const result = await rcx.verify(imageDataUrl, { expectedRecipientName: 'Ayo David', expectedBankName: 'GTBank', expectedAmount: 5000, expectedAccountNumber: '0123456789', }); if (result.valid) { console.log('Receipt is genuine ✓'); } else { console.warn('Fraud detected:', result.reason); }
npm / pip packages
Health Check
Use this unauthenticated endpoint to check if the API is up before making requests.
/health
No auth required
{ "status": "ok", "service": "ReceiptorX", "timestamp": "2026-06-27T12:00:00.000Z" }