How to build an on-pack instant-win campaign with an API
A printed code under a cap, a campaign site, a backend and a prize. This guide shows how all the pieces fit together when the prize engine is an API, and which piece is responsible for what. The docs have the exact requests; this is the map.
Architecture
- Packaging Printer
- Unique promotion code or QR token on each pack LuckLogic
- Consumer campaign site collects the code Agency
- Agency backend forwards it with a participant reference Agency
- LuckLogic API decides: eligibility, rules, Winning Moments, allocation LuckLogic
- Win or no win, returned to the site and shown to the consumer Agency
- Claim created for every win, with a token and a status LuckLogic
- Fulfilment: digital, shipped, in-store or manual Brand or agency
Two boundaries matter. The consumer only ever talks to your site. Your site only ever talks to your backend. Your backend is the only thing that holds a LuckLogic secret key.
1Create the campaign
A campaign is the container for everything else: its window, its prize tiers and its rules. Configure it
in the portal or with POST /v1/campaigns:
- Dates and timezone. Redemptions are accepted only while the campaign is active and inside the window.
- Prize tiers, each with a name, a quantity and a distribution for its Winning Moments: even, late-stage or uniform, optionally inside its own window.
-
Entry limits: a
rate_limitsuch as three redemptions per participant per day. - Security profile: standard, enhanced or high-value. It sets per-IP ceilings, invalid-code budgets, captcha, which prize ranks need manual approval and default claim expiry.
- Fulfilment per tier: digital, shipped, in-store, event or manual, and how it is verified.
Do this in sandbox first. A sandbox campaign behaves exactly like a production one and adds force-win and reset for testing. When the configuration is right, duplicate it to production.
2Generate code batches
Codes are generated in the background in batches:
POST /v1/campaigns/{id}/generate
with a volume returns a job, and the job returns a signed CSV link when it is ready.
- Multiple batches are supported on every plan. The total number of codes across batches is what the plan allows, not the number of batches.
- Batches identify different sources or print runs: 500,000 for the first run, 250,000 for the top-up in August, 10,000 for the free-entry route. Each has its own job and its own export.
-
A batch that has not been used can be revoked if the print file is lost or the run is
misprinted. Its codes then answer
CODE_NOT_FOUND. -
Each batch is either typed codes or QR tokens, exported as
token,urlso the printer can place a scannable URL on pack.
3Print the codes
By default codes use a 28-character alphabet with 0 O 1 I 8 B 5 S removed, so
a smudged or mistyped character does not become someone else's code. Ten characters is the default length.
A numeric-only alphabet is available where the pack can only carry digits; it needs more length for the
same volume.
The code itself does not contain the winning outcome. It is not a ticket, it is an entry: a random, unguessable identifier that proves the consumer has a pack and can be used once. Whether it wins is decided when it is redeemed, by Winning Moments. The print file can be handled like any other print file.
4Collect the entry
The campaign frontend collects the code and whatever else the campaign needs: an age gate, terms, a way to identify the participant for limits. That page is entirely yours. LuckLogic does not need the consumer's name, email or phone number, so collect those only if the campaign itself needs them, and keep them in your systems.
Case, spaces and hyphens in the typed code are ignored by the API, so the input can be forgiving. If the campaign uses QR tokens, the scan URL lands on the hosted entry page, or your own page if you set the base URL to it.
5Send it from the backend
The browser posts the code to your backend. Your backend posts it to LuckLogic with its secret key. The key never reaches the browser, because anything in the browser can be read, replayed and scripted. A key in a page would let anyone redeem codes against the campaign from anywhere.
curl https://api.lucklogic.dev/v1/redemptions \
-H "Authorization: Bearer $LUCKLOGIC_SECRET_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "campaign_id": "camp_…", "code": "km7rx-4q9cn", "participant_id": "crm_12345" }'
Send a fresh Idempotency-Key with every attempt and reuse it on retries. For a
page with no backend at all, use the widget with a publishable key instead; it is locked to one campaign
and your origins and cannot do anything else.
6Determine the outcome
In one request, LuckLogic:
- verifies eligibility: the campaign is active and in its window, the code exists, is in this campaign, is unused;
- applies the campaign rules: the participant is under their limit, the API key is under its ceiling;
- checks Winning Moments: is there an open moment in any tier, oldest first;
- allocates the prize if there is one, locking it so it can be won exactly once;
- creates the redemption, consuming the code whatever the outcome;
- creates a claim where there is a prize, auto-approved for digital and in-store prizes, pending for shipped and high-value ones.
The response is outcome: "win" with a prize and a
claim_token, or outcome: "lose". Anything that is
not a valid redemption is an error with a stable code: CODE_NOT_FOUND,
CODE_ALREADY_REDEEMED, RATE_LIMIT_EXCEEDED and the
rest are listed in the docs.
7Handle the result
Your frontend shows the appropriate experience: a win screen per prize tier, a no-win screen, and distinct
messages for an unknown code, a used code and an ended campaign. Store the
claim_token against your user; it is how the prize is collected and how you
look the claim up later.
A prize.claimed webhook fires for every win and
claim.updated for every status change, signed and retried, so your CRM or
fulfilment system can react without polling.
8Fulfil the claim
LuckLogic manages the claim's workflow and state: pending, approved, fulfilled, rejected or expired, with an append-only history of who did what. The fulfilment itself is done by whoever the campaign says: the brand's team in the Brand Portal, your own operations people through the API, an external fulfilment house fed by webhooks, or store staff scanning the claim with Verify at pickup.
- Digital prizes auto-approve; your system can issue the voucher the moment the webhook arrives.
- Shipped and high-value prizes wait for approval, then are marked fulfilled with a note.
-
In-store and event prizes are collected with
POST /v1/claims/{token}/collect, optionally at a named location. - Claims that pass their expiry are expired automatically and show up as such in reporting.
9Reconcile
When the campaign completes, the stats per tier give codes generated, entries redeemed, prizes won, claims by status and moments left unawarded. For every tier, won equals fulfilled plus expired plus whatever is still open in the claim period. The entry log holds every attempt for 12 months, and the change log holds every edit to a live production campaign with its reason. That is what the brand's finance and legal teams ask for, and it is already there.
Build your first campaign in Sandbox
The whole flow above can be run end to end in the Sandbox in an afternoon, with up to 5,000 test codes and no card. Force a win to see the claim path, reset the campaign and run it again.