Reference

Promo abuse

Check an offer before applying it. Report what happened afterward.

Server-side endpoint

POST https://nolgard.com/v1/promo
{"secret":"sk_…","code":"WELCOME-123","accountId":"user_42","ip":"198.51.100.9","campaign":"fall","result":"attempt"}

{"decision":"allow","reasons":[]}

Keep the secret and this call on your server. Gate the discount on decision: allow, limit, or block. A limit asks your backend to reduce or review the offer; Nolgard does not apply coupons. Fail closed on transport errors.

After processing, send the same fields with result: "redeemed", "invalid", or "rejected". Only redeemed contributes to redemption detectors; only invalid contributes to enumeration. An attempt records no redemption. Reports are not idempotent: send each actual outcome once.

Required: secret, nonempty code (up to 256 characters), result. Optional: accountId (nonempty, up to 256), IP address, campaign (up to 64), and a Nolgard browser token minted with action redeem. The token must be unexpired and belong to this site; it provides device identity without consuming verification. Alternatively, accountId uses device hashes from its last verification. Send the real client IP instead of your server IP. With no identity, only code-leak history can apply.

Code matching is exact and case-sensitive. Campaign omission groups codes into an unnamed campaign. Raw codes are never stored: a per-site HMAC identifies them. No raw IPs or account identifiers are stored in promo history.

Default detectors

ReasonDefaultDecision
promo_enumeration5 invalid codes per visitor or fingerprint, or 20 per IP prefix in a rolling hourblock for one hour from detection
promo_multi_redeem2 distinct codes in one campaign within 30 days, on a device or linked-account cluster; or one code across linked accountslimit
promo_leakedOne code redeemed from 10 distinct IP prefixes in one hourlimit, including later attempts

Leak detection observes distribution; it does not crawl coupon sites. A newly leaked code queues one promo.abuse webhook with {codeHashPrefix, campaign, reason: "promo_leaked", redemptions}. Leak markers persist until cleared. Event history is pruned after 30 days.

Verify calls with action redeem consult existing enumeration blocks and campaign redemption history. Code-specific leak checks require /v1/promo, since verify does not receive the code. The business endpoint returns explicit decisions independent of the site's observe mode.

Dashboard and thresholds

Site settings → Promo abuse lists leaked code hash prefixes and campaigns, redemption counts, and top invalid-code sources from the past hour. Clear removes promo history, leak markers, and enumeration blocks, but keeps thresholds.

GET /api/sites/:siteKey/promo
PATCH /api/sites/:siteKey/promo
{"deviceInvalid":5,"networkInvalid":20,"campaignCodes":2,"leakedPrefixes":10}
DELETE /api/sites/:siteKey/promo

These dashboard endpoints require an authenticated site owner. PATCH requires all four integer thresholds, each from 2 through 10000. Public promo traffic is rate-limited to 120 calls per minute per site. Invalid input returns 400, invalid secret 401, demo writes 403, rate limit 429.

Templated email farms

Signup verification and Supabase signup hooks count distinct accounts with the same local-part skeleton and domain in 24 hours. Letters collapse to a, digit runs to 9; separators remain and plus tags are stripped. Existing email normalization still applies, including Gmail dot removal. Five signups trigger email_pattern_burst when their numeric parts fit within a range of 50 or their first four letters match. Freemail requires the shared prefix: Gmail john.smith1984 and jane.doe1990 do not match only because their years are close.

This is an abuse reason with weight 30 rather than a bot-score signal. email_random_local records high-entropy locals of at least 12 characters with long consonant clusters as telemetry, weight zero. It never contributes to the score or repeat-limit threshold. Pattern, domain and prefix keys are hashed; counts expire after 24 hours.

Testing and limits

Test secrets are stateless: test-allow-secret and test-shadow-secret allow, test-limit-secret limits with promo_multi_redeem, and test-bot-secret blocks with promo_enumeration. They write no events or webhooks. Demo is read-only.

Thresholds are logical defaults rather than measured guarantees. Families sharing devices or one single-use code can be limited. Corporate NAT and carrier networks share IP prefixes; tune network thresholds before enforcement. Popular public offers can resemble leaks. Treat all codes as single-use across linked accounts when integrating this detector; a legitimately reusable family code needs a different offer policy. Device changes, varied email prefixes, and missing identifiers reduce coverage. Attempt checks are advisory rather than atomic coupon reservations: your backend must enforce redemption uniqueness. Independent overlapping reports can cross a threshold concurrently.