Docs
grovs.io Start for free
Examples for

Event stream

The pull feed lets your own job read a project's analytics and purchase events. Webhook and S3 destinations, exports and feed tokens are managed in the dashboard. See the event stream guide.

Availability: paid grovs Cloud plans and the self-hosted Enterprise image. Other instances get 402.

Auth: a feed token (est_…). An instance admin creates it in Developers › Data Export › Pull feed tokens › Create feed token. The token is shown once. It reads one project environment and cannot change anything.

Delivery is at least once, so deduplicate on the event id. Order is kept per device. Captured events are kept for 7 days. Capture starts when a project gets its first destination or feed token. Earlier events are only reachable through an export in the dashboard.

Find the project ID

The feed path takes the numeric ID of the project environment where you created the token. Production and test have different IDs.

It is not the project.id hashid inside events. The dashboard does not show the numeric ID yet. To find it:

  1. Open Developers › Data Export › Pull feed tokens.
  2. Open your browser's network panel.
  3. Click Refresh tokens.
  4. Read the number in the request path /api/v1/projects/PROJECT_ID/event_stream_tokens.

A token used with another project's ID gets 403.

Text
/api/v1/projects/123/event_stream
                 ^^^
                 project ID (integer)

Read the pull feed

GET/api/v1/projects/{project_id}/event_stream

Returns a page of captured analytics and purchase events, merged in arrival order. grovs keeps no position for you. Save next_after after you process the page, then send it back as after. Events become readable about 15 seconds after they arrive. The event stream guide walks through a full polling loop.

AuthScoped keyAuthentication ›

Send a feed token as Authorization: Bearer est_….

Path
project_idintegerRequired
The numeric ID of the project environment the token belongs to. See Find the project ID.
Query
afterstringOptional
The next_after of your last page, unchanged. Omit it to start at the oldest captured event (up to 7 days back).
limitintegerOptional
1 to 1000. Default 100. Caps analytics and purchases together.
event_types[]stringOptional
Repeatable filter, for example event_types[]=install&event_types[]=purchase. Unknown values are ignored. Default: every type.

Returns schema_version, events (an array of event envelopes, without geo.city or geo.ip) and next_after. An empty events array means you have caught up.

In production, each feed token can make 120 requests a minute. Over that, the API answers 503.

Errors

400`after` is not a cursor this API returned, or `limit` is not an integer.
401Missing, invalid or revoked token.
402The instance is not on a plan that includes the event stream.
403The token belongs to another project.
503More than 120 requests a minute for this token. Back off and retry with the same cursor.
Request · cURL
curl -X GET "https://api.sqd.link/api/v1/projects/{project_id}/event_stream?limit=500&event_types%5B%5D=install&event_types%5B%5D=purchase" \
  -H "Authorization: Bearer $GROVS_STREAM_KEY"
JSON
{
  "schema_version": 1,
  "events": [
    {
      "schema_version": 1,
      "id": "3f9a1c2e7b5d4e0a9c8b7a6f5e4d3c2b",
      "type": "install",
      "name": null,
      "occurred_at": "2026-10-22T09:03:11.412Z",
      "project": { "id": "pQx7", "test": false },
      "link": null,
      "attribution": { "link_visitor_id": null, "inviter_id": null, "sdk_generated": false },
      "device": {
        "id": "D8mz",
        "platform": "android",
        "os": "Android",
        "os_version": "15",
        "app_version": "4.2.0",
        "build": "4210",
        "model": "Pixel 9",
        "timezone": "Europe/Berlin",
        "language": "de"
      },
      "user": { "id": null, "attributes": {} },
      "geo": { "country": "DE" },
      "session_id": "s_8c21f0",
      "screen": null,
      "engagement_time": 0,
      "properties": {},
      "caused_by": null,
      "purchase": null
    },
    {
      "schema_version": 1,
      "id": "b71e04c9d2a35f8e6c1b0a9d8e7f6a5b",
      "type": "purchase",
      "name": "buy",
      "occurred_at": "2026-10-22T09:05:40.218Z",
      "project": { "id": "pQx7", "test": false },
      "link": null,
      "attribution": { "link_visitor_id": null, "inviter_id": null, "sdk_generated": false },
      "device": { "id": "D8mz", "platform": "android" },
      "user": { "id": "member-4471", "attributes": {} },
      "geo": {},
      "session_id": "s_8c21f0",
      "screen": null,
      "engagement_time": 0,
      "properties": {},
      "caused_by": null,
      "purchase": {
        "transaction_id": "GPA.3312-8841-2290-11873",
        "original_transaction_id": null,
        "product_id": "medly_plus_yearly",
        "purchase_type": "subscription",
        "store": "google",
        "quantity": 1,
        "currency": "EUR",
        "usd_price_cents": 5399,
        "purchase_date": "2026-10-22T09:05:38.000Z",
        "version": "2026-10-22T09:05:41.006Z"
      }
    }
  ],
  "next_after": "eyJlIjpbIjIwMjYtMTAtMjIgMDk6MDM6MTEuNDEyIiwiM2Y5YSJdLCJwIjpbIjIw…"
}

The event envelope

Every event has the same JSON shape on every transport: pull feed, webhook and S3. This is envelope version 1. New keys may appear within a version. A rename or removal comes as a new schema_version.

Attributes
schema_versioninteger
Always 1 for this shape.
idstring
The deduplication key. For a purchase it changes with every store correction.
typestring
view, open, install, reinstall, app_open, time_spent, reactivation, user_referred, screen_view, custom, purchase, or test for a test event sent from the dashboard.
namestring | null
The custom event's name, or the store action of a purchase: buy, cancel, refund, refund_reversed. null otherwise.
occurred_atstring
When the event happened, ISO 8601 with milliseconds.
projectobject
id (hashid) and test (whether it is the test environment).
linkobject | null
id and campaign_id (integers), path, source, medium, campaign, ads_platform, tags. null when the event has no link.
attributionobject
link_visitor_id and inviter_id (hashids or null), sdk_generated.
deviceobject
id (hashid), platform, os, os_version, app_version, build, model, timezone, language. Purchases carry id and platform only.
userobject
id is the identifier your app set through the SDK, attributes its attributes. Use user.id to join with a CRM.
geoobject
country. Destinations with include_ip also get city and ip. The pull feed never includes them.
session_idstring | null
As recorded by the SDK.
screenstring | null
Screen name, as recorded by the SDK.
engagement_timeinteger
As recorded by the SDK. 0 for purchases.
propertiesobject
Event properties.
caused_bystring | null
On user_referred, the id of the install that produced it.
purchaseobject | null
On a purchase: transaction_id, original_transaction_id, product_id, purchase_type, store, quantity, currency, usd_price_cents, purchase_date, version.

Stores correct purchases after the fact. Each correction arrives as a new event with a newer purchase.version. Within a project, upsert on (purchase.transaction_id, name) and keep the newest purchase.version.

JSON
The event envelope
JSON
{
  "schema_version": 1,
  "id": "3f9a1c2e7b5d4e0a9c8b7a6f5e4d3c2b",
  "type": "install",
  "name": null,
  "occurred_at": "2026-10-22T09:03:11.412Z",
  "project": { "id": "pQx7", "test": false },
  "link": {
    "id": 8812,
    "path": "flu-shot",
    "campaign_id": 41,
    "source": "email",
    "medium": "newsletter",
    "campaign": "autumn_checkup",
    "ads_platform": null,
    "tags": ["q4"]
  },
  "attribution": { "link_visitor_id": "V1k9", "inviter_id": null, "sdk_generated": false },
  "device": {
    "id": "D8mz",
    "platform": "ios",
    "os": "iOS",
    "os_version": "18.6",
    "app_version": "4.2.0",
    "build": "4210",
    "model": "iPhone16,2",
    "timezone": "Europe/Berlin",
    "language": "de"
  },
  "user": { "id": "member-4471", "attributes": { "plan": "plus" } },
  "geo": { "country": "DE" },
  "session_id": "s_8c21f0",
  "screen": "Home",
  "engagement_time": 0,
  "properties": {},
  "caused_by": null,
  "purchase": null
}
Was this page helpful?
Last updated 2026-10-26