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:

OutcomeMeaning
wonEntered and won a reward. reward is populated.
lostEntered but didn't draw a reward. reward is null.
bundle_in_progressCollect-all bundle — this scan counted, more SKUs needed.
needs_termsLive promotion with unaccepted T&Cs — scanner must accept to enter.
not_signed_inA live promotion applied, but the scanner wasn't signed in to enter.
JSON
{
  "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.

JSON
{
  "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).

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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" } ]
  }
}

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.

JSON
{
  "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.

JSON
{
  "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.

JSON
{
  "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.