@cashoutguard/node
v1.0.0
Published
Node.js client for the CashoutGuard fraud-prevention API (rewards, GPT and offerwall sites).
Maintainers
Readme
@cashoutguard/node
Node.js client for CashoutGuard, the fraud-prevention API for rewards, GPT and offerwall sites.
- Node 18 or newer. No runtime dependencies: it uses the global
fetchandcrypto. TypeScript types are included. - Fail-open by default. A timeout, network error, non-2xx answer, invalid JSON or invalid argument resolves to an
allowresult witherrorset. It never throws. - 2 second total timeout by default.
Install
npm install @cashoutguard/nodeQuickstart
1. Browser: on every page
<script src="https://cashoutguard.com/v1/agent.js" data-key="pk_live_YOUR_PUBLIC_KEY" async></script>Collect a request_id on the key moment and send it with your form:
CashoutGuard.ready(async () => {
const { request_id } = await CashoutGuard.collect({ event: 'cashout' });
document.querySelector('#cashout-form [name=cg_request_id]').value = request_id ?? '';
});2. Server: ask for a decision
import { CashoutGuard } from '@cashoutguard/node'; // or: const { CashoutGuard } = require('@cashoutguard/node');
const cg = new CashoutGuard(process.env.CASHOUTGUARD_SECRET);
app.post('/cashout', async (req, res) => {
const risk = await cg.evaluate({
event: 'cashout', // signup | login | offer_click | conversion | cashout | custom
account_id: req.user.id, // your own user id
request_id: req.body.cg_request_id,
ip: req.ip,
email: req.user.email,
payout_address: req.body.paypal_email,
payout_method: 'paypal',
amount: Number(req.body.amount),
currency: 'USD',
});
if (risk.failed) console.warn('CashoutGuard unavailable, failing open:', risk.error);
if (risk.isBlocked()) return res.status(403).json({ error: 'on_hold' });
if (risk.needsReview()) await queueForReview(req.user, risk.raw);
else await payNow(req.user, req.body.amount);
res.sendStatus(204);
});Offerwall conversions (postbacks)
Score the conversion from your postback handler. Send the same click_id you used on offer_click; no request_id is needed because the conversion inherits the device of its click.
const risk = await cg.evaluate({
event: 'conversion',
account_id: userId, // networks: "publisherId:userId"
offer_id: offerId,
offer_name: offerTitle, // shown in the dashboard
click_id: clickId,
transaction_id: txId,
amount: payout,
ip: userIp, // if the offerwall sends it
source: 'torox', // offerwall, network or publisher
meta: { sub1 }, // up to 20 extra strings
});Result
| Member | Meaning |
|---|---|
| isBlocked() / needsReview() / isAllowed() | Based on action. Use these. |
| action | allow, review or block: what to do. In monitor mode it is always allow. |
| decision | allow, review or block: what the rules concluded, even in monitor mode. |
| score | 0–100, or null when the call failed. |
| reasons / reasonCodes() / hasReason(code) | [{ code, weight, detail? }], highest weight first. |
| raw | Full JSON response: account, device, network, linked_accounts, cashout, and so on. |
| eventId, mode, account, accountStatus() | Convenience accessors. |
| ok / failed, error, errorMessage, status | error is null on success. Otherwise it is a short code such as timeout, network_error, http_500, temporarily_unavailable, missing_or_invalid_secret_key, validation_failed, not_found, invalid_argument, invalid_response or missing_secret_key. |
evaluate() sends only the documented fields. It drops optional values that the API would reject, such as a malformed IP or request_id, or a negative amount, and it truncates over-long strings.
Accounts
const acc = await cg.account('user_48213'); // GET /v1/accounts/{id}
if (acc.error === 'not_found') { /* never evaluated */ }
await cg.setAccountStatus('user_48213', 'blocked'); // active | allowlisted | blockedWebhooks
Each webhook is signed with X-CashoutGuard-Signature = hex HMAC-SHA256 of the raw body, keyed with your webhook secret. The check uses crypto.timingSafeEqual.
import express from 'express';
import { verifyWebhook } from '@cashoutguard/node';
app.post('/webhooks/cashoutguard', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifyWebhook(req.body, req.get('X-CashoutGuard-Signature'), process.env.CASHOUTGUARD_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8')); // { type: 'account.blocked', created_at, data: { account_id, score, event_id } }
res.sendStatus(204);
});Options
new CashoutGuard('sk_live_...', {
baseUrl: 'https://cashoutguard.com', // default
timeoutMs: 2000, // whole request
failOpen: true, // false = reject with CashoutGuardError { code, status, body }
fetch: customFetch, // default: global fetch
});Development
npm install
npm test # tsc -> dist/, then node --testLicense
MIT
