Reference
Event catalog.
Every event onpack emits, with the exact payload your endpoint will receive. Subscribe to any combination when you register an endpoint.
scan.processed
The headline event. Fires for every production scan with the full outcome of what happened — the scan itself, who it's tied to, and which promotion was evaluated (if any), with its result.
Fires when: a shopper scans a production QR and the scan
flow resolves. Also fires when a previously-anonymous scan is retroactively
credited to a new signup within 15 minutes — the payload carries
retroactive_credit: true in that case. Silent on within-window
dedupes.
Eligibility outcomes you'll see in eligibility[].outcome:
| Outcome | Meaning |
|---|---|
won | Entered and won a reward. reward is populated. |
lost | Entered but didn't draw a reward. reward is null. |
bundle_in_progress | Collect-all bundle — this scan counted, more SKUs needed. |
needs_terms | Live promotion with unaccepted T&Cs — scanner must accept to enter. |
not_signed_in | A live promotion applied, but the scanner wasn't signed in to enter. |
{
"id": "4b0e…c912",
"event": "scan.processed",
"occurred_at": "2026-04-23T15:04:12Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"scan": {
"id": 184293,
"unit": { "id": 9102, "code": "NFD-HNY-260412-7HGQ2" },
"sku": { "id": 31, "name": "Northfield Wildflower Honey 340 g", "prefix": "NFD-HNY" },
"batch_id": 22,
"country": "DE", "city": "Berlin",
"device_type": "mobile",
"scanned_at": "2026-04-23T15:04:12Z",
"retroactive_credit": false
},
"collector": { "id": 55821, "identified": true },
"points_awarded": 10,
"eligibility": [
{
"activation": { "id": 8, "name": "Spring Instant Win", "activation_type": "instant_win" },
"outcome": "won",
"entry_id": 71284,
"reward": { "id": 44, "name": "5€ voucher", "reward_type": "coupon", "tier": "standard", "face_value": 5.0 },
"bundle_progress": null
}
]
}
}
activation.entered
Fires for every production promotion entry — wins and losses. Use this when you want honest funnel data, not just the winners.
{
"event": "activation.entered",
"occurred_at": "2026-04-23T15:04:12Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"activation_entry": { "id": 71284, "outcome": "lost" },
"activation": { "id": 8, "name": "Spring Instant Win", "activation_type": "instant_win" },
"reward": null,
"consumer": { "id": 55821 },
"scan": { "id": 184293 },
"unit": { "id": 9102, "code": "NFD-HNY-260412-7HGQ2" },
"sku": { "id": 31, "name": "Northfield Wildflower Honey 340 g", "prefix": "NFD-HNY" }
}
}
activation.win
Convenience event — strict subset of activation.entered that
only fires on wins. Same payload shape. Subscribe to this if you want a
simpler "someone won something" trigger without filtering on the consumer side.
reward.redeemed
Fires when a collector redeems a reward at point-of-fulfilment. Includes
the redemption status, reward details, consumer id, and any onpack fee
involved (the key is packd_fee — the platform's former name,
kept on the wire so no consumer breaks).
{
"event": "reward.redeemed",
"occurred_at": "2026-04-24T11:02:19Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"redemption": { "id": 9911, "status": "pending", "voucher_code": "NFD-7K2Q-M4", "packd_fee": 0.0 },
"reward": { "id": 44, "name": "5€ voucher", "reward_type": "coupon", "points_required": 250, "face_value": 5.0 },
"consumer": { "id": 55821 }
}
}
batch.generated
Fires when a batch of unit codes finishes generating — codes are now
printable. batch.export_ready fires shortly after, same
payload plus an export block with checksum and row count,
when the downloadable file is available.
batch.export_ready
The file is downloadable at GET …/batches/:id/download; the
checksum and row count are what the print vendor verifies against.
{
"event": "batch.export_ready",
"occurred_at": "2026-04-12T08:33:40Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"batch": { "id": 22, "number": 12, "quantity": 100000, "purpose": "production", "generated_at": "2026-04-12T08:31:02Z" },
"sku": { "id": 31, "name": "Northfield Wildflower Honey 340 g", "prefix": "NFD-HNY" },
"export": { "ready": true, "generated_at": "2026-04-12T08:33:40Z", "row_count": 100000, "checksum_sha256": "9f2c…e1a0" }
}
}
Pack Identity · passport.published
Fires every time a product's passport is published — from the editor, an
import, a four-eyes approval, or a restore over the API. Carries
the revision number, whether this is the first publication, which fields and
which languages changed since the previous revision, and both addresses the
page answers at. This is the event a catalogue or retailer feed listens to
in order to stay in step with what the pack actually says; the content
itself is one GET away on either URL.
{
"event": "passport.published",
"occurred_at": "2026-08-30T09:12:41Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"passport": {
"revision": 4,
"source": "ui",
"published_at": "2026-08-30T09:12:41Z",
"first_publication": false,
"changed_fields": ["ingredients", "storage"],
"changed_languages": ["en"]
},
"sku": {
"id": 31, "slug": "wildflower-honey-340-g", "name": "Northfield Wildflower Honey 340 g",
"gtin": "09524000123459",
"public_url": "https://onpack.io/01/09524000123459",
"gs1_url": "https://onpack.io/01/09524000123459"
}
}
}
Pack Identity · document.expiring
Fires when a certificate or report attached to a passport approaches its
expiry — 60, 30, 14, 7, 1 days
before, on the day, and once after it has lapsed — on the same days the
email goes out, so a compliance system can act without anyone reading an
inbox. shown_on_page is the urgency: an expired document is
already off the public page, an expiring one is still being cited.
{
"event": "document.expiring",
"occurred_at": "2026-12-10T05:00:00Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"document": {
"id": 12, "title": "AMA-Gütesiegel 2026", "kind": "certificate",
"issuer": "AMA-Marketing", "reference": "AT-2026-0417",
"issued_on": "2026-01-10", "expires_on": "2027-01-09",
"days_until_expiry": 30, "status": "expiring",
"shown_on_page": true, "presentable": true
},
"skus": [ { "id": 31, "slug": "wildflower-honey-340-g", "gtin": "09524000123459" } ]
}
}
Tools · link_code.scanned
Fires every time a person scans a link code — the QR on a poster, a flyer, a menu. Carries the code as the API returns it, where that scan was sent (the scheduled page and the brand's tracking applied), the country from the edge and the kind of phone. Crawlers are recorded but never published. Tools belong to no product, so this event is offered to any company with an active plan.
{
"event": "link_code.scanned",
"occurred_at": "2026-10-01T10:02:11Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"link_code": { "token": "k7m", "label": "Trade-fair poster", "short_url": "https://onpack.io/l/k7m",
"destination_url": "https://northfield.example/summer", "scans_count": 213, "access": "live" },
"scan": { "country": "AT", "platform": "ios",
"destination": "https://northfield.example/summer?utm_source=onpack&utm_medium=qr&utm_campaign=trade-fair-poster" }
}
}
Tools · outreach.opened
Fires every time a recipient of an outreach mailing
opens the page behind the code in their letter. open.first is
true on the one that counts as the letter being read — the same open that
mails the brand — and false on every reload after it. The recipient is the
full API record, so a CRM can match on reference without a
second call.
{
"event": "outreach.opened",
"occurred_at": "2026-10-03T07:41:09Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"outreach": { "id": 12, "name": "Hotels Tirol, autumn 2026", "headline": "Breakfast for {{company}}", "archived": false },
"recipient": { "token": "aqpepqtr3rfa", "reference": "H-1001", "company": "Hotel Post", "name": "Maria Huber",
"page_url": "https://onpack.io/o/aqpepqtr3rfa", "opened": true, "opens": 1, "engaged": false, "engagements": 0 },
"open": { "first": true, "country": "AT", "platform": "ios" }
}
}
Tools · outreach.engaged
Fires when an outreach recipient taps the button (kind: "cta")
or a product card (kind: "product") on their page — the signal
that the letter was worth something, and the moment to call.
engagement.first marks the first tap by that recipient. Sent
by a beacon from the page, so a mailto: or tel:
button still opens directly; a tap on a preview (test_url)
never fires it.
{
"event": "outreach.engaged",
"occurred_at": "2026-10-03T07:41:52Z",
"brand": { "id": 1, "slug": "northfield", "name": "Northfield", "prefix": "NFD" },
"data": {
"outreach": { "id": 12, "name": "Hotels Tirol, autumn 2026", "headline": "Breakfast for {{company}}", "archived": false },
"recipient": { "token": "aqpepqtr3rfa", "reference": "H-1001", "company": "Hotel Post", "name": "Maria Huber",
"page_url": "https://onpack.io/o/aqpepqtr3rfa", "opened": true, "opens": 1, "engaged": true, "engagements": 1 },
"engagement": { "kind": "cta", "first": true }
}
}
webhook.test
A synthetic event triggered by the Send Test button in the dashboard. Useful for verifying signature handling before subscribing to real traffic.
Subscribing
Webhook endpoints are registered from Settings → Webhooks in
your brand dashboard, or over the API. Each endpoint subscribes to a
specific set of events — you can opt into all, some, or one — from among
the events of the products your company owns. The Tools events
(link_code.*, outreach.*) belong to no product and
are offered to any company with an active plan.