unPLUGDevelopers

App events

Send what guests do in your app, such as viewing an item or starting checkout, so unPLUG can react to it. Abandoned-cart messages and funnel reports both run on these events.

Available at launch

Why send events#

unPLUG already knows about orders and points from the API. It doesn't know what happened before the order, or why there wasn't one. App events fill that gap:

  • Journeys. A journey can start on an app event. For example: added_to_cart with no order within an hour sends a push with the guest's cart.
  • Reports. unPLUG builds abandoned-cart and funnel reports (menu, item, cart, checkout, order) from them, so the brand can see where guests drop off.

Don't send orders as events. unPLUG already gets them from POST /v1/orders, and points earned at the register also show up.

Use these names so unPLUG's reports and journey templates pick them up without mapping. You can add your own names in snake_case too.

EventSend it whenProperties
app_openedThe app comes to the foregroundsource: direct, push or deep_link. message_id when opened from a push.
session_startedA new session starts: launch, or back after 30 minutes awayNone
screen_viewedA screen appearsscreen: home, menu, rewards, cart, checkout
product_viewedAn item's detail openspos_item_id, name, price_cents, currency, location_id
added_to_cartAn item goes in the cartcart_id, pos_item_id, name, quantity, price_cents, currency, location_id
removed_from_cartAn item comes out of the cartcart_id, pos_item_id, quantity
checkout_startedThe guest opens checkoutcart_id, subtotal_cents, currency, item_count, location_id, fulfillment
signed_upA new account is createdmethod: email, phone, apple, google
signed_inAn existing account signs inmethod
push_permission_changedThe OS notification permission changesenabled: true or false
reward_viewedA reward or wallet item's detail opensreward_id, or wallet_item_id

Use the same pos_item_id values you send at checkout (Square catalog variation ids) and a cart_id that stays the same for one cart, so unPLUG can follow a cart from first item to order.

Ids on every event#

FieldWhat it is
event_idA UUID you create when the event happens. unPLUG drops any event whose event_id it has already seen, so retries never count twice.
anonymous_idA UUID you create once per install, before the guest signs in. Keep sending it after sign-in.
device_idThe same id you use to register the device.
session_idA new UUID per session.
occurred_atWhen it happened on the device, not when you sent it.

Before and after sign-in#

A guest often browses the menu before they sign in. Those events matter for the funnel, but the app has no member token yet.

  1. Before sign-in, send events to your own server, and have it forward them to unPLUG with the secret key and anonymous_id.
  2. After sign-in, send them straight from the app with the member token. The member is taken from the token. Keep the same anonymous_id on the events, so the browsing before sign-in and the order after it read as one visit.
  3. On sign out, go back to step 1. Keep the anonymous_id: it belongs to the install, not the guest.

Your website works the same way: a page with a member token sends directly; anonymous events go through your server.

Send a batch#

Queue events on the device and send them in batches of up to 100: every 30 seconds while the app is open, and when it goes to the background. Store the queue on disk, so events survive the app being killed.

POSTRequest from the app
curl -X POST https://api.unplugdining.com/v1/app_events \
  -H "Authorization: Bearer mt_4kQ9xV2mR7tB8nW3pL6yZ1cH5fJ0dS" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "event_id": "e2b7c4a1-9d38-4f65-8a0e-3c1f6b9d2e57",
        "name": "added_to_cart",
        "occurred_at": "2026-11-03T14:12:40Z",
        "anonymous_id": "c5f1a8e3-7b2d-4c96-91e4-0a6d3f8b2c71",
        "device_id": "9c7e2f14-58ab-4d3e-b1a0-6f2d8e47c935",
        "session_id": "1d8e4b2f-6a3c-4e91-b7d5-2f0c9a8e6b34",
        "properties": {
          "cart_id": "cart_5Tn8qW2x",
          "pos_item_id": "HV4MZ2TQ7LW3KXRB5P8NJ6DC",
          "name": "Oat latte",
          "quantity": 1,
          "price_cents": 575,
          "currency": "USD",
          "location_id": "8d4e2a17-3b9c-4f5e-a6d1-7c0b9e2f4a53"
        }
      },
      {
        "event_id": "7a3d9f0c-2e6b-4b18-a4c7-5e9b1d3f8a26",
        "name": "checkout_started",
        "occurred_at": "2026-11-03T14:13:05Z",
        "anonymous_id": "c5f1a8e3-7b2d-4c96-91e4-0a6d3f8b2c71",
        "device_id": "9c7e2f14-58ab-4d3e-b1a0-6f2d8e47c935",
        "session_id": "1d8e4b2f-6a3c-4e91-b7d5-2f0c9a8e6b34",
        "properties": {
          "cart_id": "cart_5Tn8qW2x",
          "subtotal_cents": 1000,
          "currency": "USD",
          "item_count": 2,
          "location_id": "8d4e2a17-3b9c-4f5e-a6d1-7c0b9e2f4a53",
          "fulfillment": "pickup"
        }
      }
    ]
  }'
Response202
{
  "accepted": 2,
  "duplicates": 0
}

From your server, use a secret key with the members:write scope and put member_id or anonymous_id on each event.

Retries and failures#

  • Timeouts, 429 and 5xx. Keep the batch in the queue and send it again later with the same event_ids. Duplicates are dropped and counted in duplicates.
  • 401 token_expired. Get a new member token from your server, then resend.
  • 400 invalid_param. One bad event fails the batch, and param names it (events[1].occurred_at). Drop that event and send the rest; don't retry it forever.

No personal data#

Don't put names, email addresses, phone numbers, street addresses or anything the guest typed into properties. Ids, item names, prices and screen names are fine. unPLUG already has the member's contact details, linked by the member id; it doesn't need them in events.

Warning Events are stored as sent. Check your properties in the sandbox before release, so personal data never leaves the app by mistake.

Timing#

Events reach unPLUG within about 20 minutes. That's fine for an abandoned-cart journey that waits an hour, but not for anything the guest should see right away. For that, use the API response or a webhook.

Checklist#

  • Every event has an event_id created once and reused on retries.
  • The queue is on disk and flushes when the app goes to the background.
  • Events before sign-in go through your server with anonymous_id; events after go direct with the member token.
  • pos_item_id and cart_id match what you send at checkout.
  • No personal data in properties.