Holding credits until
a payment settles
A stolen card buys credits that burn GPU hours before the chargeback lands. Stripe tells you whether the payment went through; Nolgard tells you how much compute to release before the money settles.
Decision trace
Example · first payment, 5 000 creditsHold
- Input
verify({action:'checkout'})foruser_42. Third card from this device today. - Reason ids
card_cycling - Decision
limit·payment.threeDS: true - Credits1 500 now, 3 500 after 72 h · frozen on a dispute
What it does
| Reason | Checkout | Elsewhere |
|---|---|---|
card_testing | block: failed payments ≥3/hour from one device or ≥10/hour from one network | — |
card_cycling | limit: ≥3 distinct cards from one device in 24 h · 3-D Secure and a hold | — |
payment_fraud | block: early fraud warning or fraudulent dispute on this device or a linked account in 180 days | limit · GET /v1/accounts/:id returns payment_fraud and revoke_credits |
refund_serial | limit: ≥2 refunds or lost disputes by the same human in 180 days | limit on the next free-trial signup |
Thresholds are per site. A hold caps spend at its share of the purchased credits; a fraud warning or a fraudulent dispute freezes the account until an owner releases it in the Payments tab.
How it works
Connect the webhook
One endpoint in your Stripe dashboard and the
whsec_…secret in site settings. No Stripe API key.Gate the button
Nolgard.execute({ action: 'checkout' })in the browser, then verify on the server before you create the session.Release in two parts
On
limit, release the hold share now and the rest after the hold expires. Chargebacks and refunds follow the person into the next trial.
Integration
const r = await nolgard.verify({
token, action: 'checkout',
account: { id: user.id },
checkout: { credits: 5000 },
});
if (r.decision === 'block' || r.decision === 'shadow') return deny();
const session = await stripe.checkout.sessions.create({
mode: 'payment', line_items, success_url, cancel_url,
client_reference_id: r.ref,
metadata: { nolgard_ref: r.ref, nolgard_account: user.id },
payment_intent_data: {
metadata: { nolgard_ref: r.ref, nolgard_account: user.id },
...(r.payment?.threeDS ? { payment_method_options: { card: { request_three_d_secure: 'any' } } } : {}),
},
});
// r.payment.hold → { share: 0.3, hours: 72 }: release 1500 credits now, 3500 after 72 h.Data stored
- Stripe outcome kind, livemode, amount, currency, decline or dispute reason, card country.
- HMAC of Stripe's card fingerprint, salted actor hashes. Never card numbers, names, emails or addresses.
- Payment events and links 180 days, processed event ids 30 days, released holds 30 days.
Limits
- Needs the webhook. Without delivered events there are no card outcomes and no holds.
- Stripe only for now. Other processors are not supported.
- Thresholds are uncalibrated defaults. Run them in observe and tune per site.
- Stripe Radar still decides whether the payment goes through. We decide how much compute it releases.
- Test-mode events freeze and release holds for testing, but never label devices or feed the shared network.