Custom events & API

Send your own events from your app's backend, pair them to the stores they happened for, and chart, segment and alert on them next to your Partner metrics.

What custom events are

The Partner API tells AppVitals about installs, subscriptions and payments. Custom events cover everything it can’t: onboarding steps completed, exports finished, feature usage, background jobs, errors, anything your app’s backend can observe. You send them over a small REST API (or the official Node client), and AppVitals treats them as first-class data:

  • Every event belongs to one of your apps and can be paired to a concrete store by its myshopify domain, the same key the rest of AppVitals uses
  • The Explorer charts any event with count, unique-ID and numeric-property measures, breakdowns and filters, and lets you save the result as a report
  • Slack notification rules can fire on chosen events, with a per-event message template you control
NoteCustom events are available on every plan. The official client lives on npm as @appvitals/client.

Quickstart

  1. Create an API key

    In Organization → API access create a key with the write_custom_events scope; add identify_customers if you also want to push customer profiles (below). The av_… secret is shown once at creation, store it server-side. API keys must never ship in frontend code.

  2. Send your first event with curl

    curl -X POST https://appvitals.io/api/custom-events/ingest \
      -H "Authorization: Bearer av_..." \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Export finished",
        "appId": "gid://partners/App/123456",
        "myshopifyDomain": "shop.myshopify.com",
        "insertId": "export-1024",
        "properties": { "format": "csv", "rows": 1200 }
      }'

    A 200 response reports {"inserted": 1, "duplicates": 0, "blocked": 0, "failed": []} plus your month-to-date usage. The event appears in the Custom events pages immediately.

  3. Or use the official client

    The client is published on npm: npmjs.com/package/@appvitals/client.

    npm install @appvitals/client
    import { AppVitalsClient } from "@appvitals/client";
    
    const appvitals = new AppVitalsClient({
      apiKey: process.env.APPVITALS_API_KEY!, // av_...
    });
    
    // One event; insertId makes retries safe.
    await appvitals.sendCustomEvent({
      name: "Export finished",
      appId: "123456", // Partner gid or bare numeric id
      myshopifyDomain: "shop.myshopify.com",
      insertId: "export-1024",
      properties: { format: "csv", rows: 1200 },
    });
    
    // Or a batch (max 100 per request).
    await appvitals.sendCustomEvents([...]);

    The client is server-side Node (18+). By default a failed call is logged with console.warn and resolves to null instead of rejecting, so fire-and-forget with void appvitals.sendCustomEvent(…) can never take your app down; opt into rejections with onError: "throw". To test against a dev or staging instance, set the APPVITALS_BASE_URL environment variable (or the baseUrl option) to its origin.

Ingest reference

POST /api/custom-events/ingest with Authorization: Bearer av_… (scope write_custom_events). The body is one event object, or {"events": [...]} with up to 100 events per request.

Custom event fields
FieldMeaning
nameRequired. 1 to 120 characters, starting with a letter or digit; letters, digits, spaces and _ - . : / allowed. Keep one consistent spelling per event.
appIdRequired. Your app's Partner gid (gid://partners/App/123456) or its bare numeric id. Must be one of your organization's apps.
myshopifyDomainOptional. Pairs the event to a concrete store, the same key identify uses. When distinctId is omitted, the domain doubles as the identity.
distinctIdOptional. A custom identity (user id, session id, ...) powering the Unique IDs measure. A distinctId that is itself a *.myshopify.com domain pairs the event to that store automatically. Omit both identity fields for app-level events.
timeOptional. ISO 8601 string or unix milliseconds; defaults to arrival time. Up to 5 years in the past, at most 10 minutes of future clock skew.
insertIdOptional idempotency key, 1 to 64 characters of letters, digits, _ or -. Events with the same (appId, name, distinctId, insertId) are stored once; resends count as duplicates. Without it every send creates a new event.
propertiesOptional. Up to 50 flat scalar values (string ≤ 500 chars, number, boolean, null); no nested objects or arrays. Keys are 1 to 60 characters of letters, digits, _ - or . — use dotted keys like plan.name for hierarchy; every property stays usable as a chart breakdown and filter.

Batches and partial acceptance

Batches are accepted item by item: valid events are stored, invalid ones come back in failed[] as {index, error} records aligned with your request array. An unknown appId is a per-item error too. The request only answers 400 when the envelope is malformed or no event in it was accepted. When a batch would exceed your monthly allowance without usage billing, it is accepted up to the limit and the rest fails with Monthly included event limit reached - enable usage billing to continue (see limits below).

Responses and errors

  • 200 with inserted, duplicates (insertId resends), blocked (event names you blocked in the catalog), failed[] and a usage snapshot
  • 400 malformed body, empty batch, more than 100 events, or every event invalid
  • 401 missing or invalid API key, 403 key lacks the write_custom_events scope

The complete schema with runnable examples is in the REST API reference.

Identify: customer profiles, tags & contacts

POST /api/customers/identify (scope identify_customers; client method appvitals.identify(…)) is the companion endpoint your backend calls on install and on every profile change. One payload, keyed by myshopifyDomain + appId, updates three things at once, all partially: omitted fields keep their stored values.

  • Customer tags: tags apply to the store itself (matched by its myshopify domain, no email needed), the same tags you edit in the Customers table. operators.tags controls the merge: replace (default) swaps the whole list, upsert adds missing tags and keeps the rest (append is an alias), remove deletes just the sent tags
  • Store profile & custom attributes: fields like primaryLocale, shopifyPlan, currency, timezone, createdAt and storefrontDomain carry facts from your own Admin API that the Partner feed never exposes; customFields holds app-scoped attributes merged per key: flat scalars or lists of strings, where a list replaces whole on every push and null deletes a key
  • Contact: when an email is present, the customer’s contact record is created or updated, and the same tag operation is mirrored onto the contact so email recipient conditions stay in sync

Identify is order-independent. A fresh install usually calls it before the store has appeared in your synced Partner data; the tag and profile operations are then parked (the response reports customerTagsPending / shopProfilePending instead of …Updated) and apply automatically after the next sync makes the store resolvable.

Limits & billing

Every plan includes a monthly allowance of custom events, counted per calendar month (UTC). Only newly stored events count: insertId duplicates and blocked events never do.

Included custom events per month by plan
PlanIncluded events / month
Free500
Tier 1-450,000
Tier 5+ and Enterprise250,000
  • Usage billing (opt-in): enable it on the Billing page and ingest continues past the allowance at $1 per 1,000 events over it, added to your next invoice
  • Without the opt-in: once the allowance is used up, new events are rejected (the UI shows “Events paused, limit reached”) until your usage resets at the start of the next month; nothing already stored is lost
  • Organization owners get an email at 90% and again at 100% of the allowance, once per month each
  • A usage meter on the Custom events pages and a Monthly usage card on the Billing page show month-to-date events, the included allowance and the overage state at any time

Using the data

The Custom events sidebar entry has four tabs:

  • Home: your saved reports as live cards, the place your team lands
  • Explorer: the chart builder. Up to several metrics per chart, each an event with a measure (count, unique IDs, or sum / average / min / max over a numeric property), bucketed by day, week or month, with property filters, a group-by breakdown and a previous-period comparison. Save any result as a report
  • Events: the newest-first raw feed with name and app filters, plus the event catalog where you describe, hide or block event names (blocked names are dropped at ingest)
  • Users: every identity that ever sent an event (its distinctId, or the store domain), with search and latest activity

Custom events also plug into Slack notification rules: a rule row picks an event name and renders a message template with Liquid placeholders ({{ properties.plan }}, {% if %}…) against each incoming event, so the Slack line reads exactly how your team wants it.

Custom Events: Track Anything via the API · AppVitals