Set up product event tracking.
From nothing to each client’s usage on their Revvly page: switch it on, issue a key, tell Revvly who your clients are, send events from your backend and, if you want page views and time on page, add the browser script. Nothing sent this way is billed.
#Give this prompt to an integration agent
It covers every step on this page. Copied from the dashboard (Settings, Product events, Setup guide) it also carries your tenant id.
You are adding product usage tracking to the operator's product so Revvly shows how each of the operator's clients uses it: events sent from the operator's backend and, optionally, page views and time on page sent from the browser with a short-lived token.
Working agreement before you implement:
- Do not write production code until you have enough concrete context from the operator.
- Ask clarifying questions first — prefer one short round of questions over guessing.
- Confirm at least: source system / trigger, field mapping, where the API key and any signing secret will be stored, the target environment (local, staging, or production), and the Revvly tenantId when it is not already provided below.
- Once those answers are in place, implement the full integration against the contract below end-to-end.
- Open or fetch the Full guide URL if you need examples or edge-case detail beyond this prompt.
- Never invent API keys, signing secrets, region ids, subscription ids, or other tenant data.
- Never put secrets in browser code, logs, client bundles, or source control.
Tenant context
- The operator has not supplied a tenantId in this prompt. Ask for the Revvly tenant id before making tenant-scoped assumptions.
Platform rules that apply to every Revvly API call:
- Auth is a Bearer API key issued in the dashboard under Settings → API Keys. Tokens are prefixed `rvly_sk_live_`.
- Rate limits are per API key and default to 10 requests/second and 100 requests/minute. Overage is HTTP 429 with a `Retry-After` header in seconds (public GraphQL reports `errors[].extensions.code = RATE_LIMITED`). Honour Retry-After and back off — never retry a 429 in a tight loop.
- A key can be scoped to specific subscriptions and meters. List reads are filtered to that scope; reading or writing a single out-of-scope resource is 403 (`FORBIDDEN`), not an empty result.
- Every call is tenant-scoped by the key itself. There is no tenant parameter to switch — a different tenant means a different key.
- The token and the signing secret are server-side credentials: no browser code, no client bundles, no logs, no source control.
Full guide: https://www.revvly.ai/docs/guides/product-events-setup
Goal
Report what the operator's users do (a report exported, a feature opened, a page viewed) so Revvly shows it on each client's Usage tab, in the cross-client overview and in the support ticket of the person who wrote in. Nothing sent this way is billed; for billable metering use the Event Ingestion API instead.
Credentials, never mixed
- API key (rvly_sk_live_…): created in the Revvly dashboard under Settings → API Keys with "Can send product events" ticked. That tick only appears once Revvly has switched Product usage on for the workspace. Server-side only: it never goes in a page, an app bundle, logs, URLs or git.
- Browser token: a short-lived (60 s to 1 h, default 15 min) token the operator's backend asks for at POST https://api.revvly.ai/api/v1/product-events/tokens, one per signed-in person. The page sends with it to POST https://api.revvly.ai/api/v1/product-events/browser. Revoking the key stops every token it minted.
Step 1 — Operator setup in the Revvly dashboard (ask the operator to confirm each is done; you cannot do it for them)
- Product usage is switched on for the workspace (Settings → Product events appears, and the key form shows "Can send product events").
- An API key with that tick exists and is stored in the backend's secret store, e.g. REVVLY_PRODUCT_EVENTS_KEY.
- Every client has an External ID (Edit client → External ID) equal to the clientId the product will send. An event whose clientId matches no client is stored but shown on no client.
- For browser tracking: every site that will send is listed under Settings → Product events → Browser tracking → Allowed sites, one per line, e.g. https://app.example.com. https://*.example.com covers every subdomain but not example.com itself, another scheme or another port; each white-label domain is its own line; at most 100 lines.
Step 2 — Send events from the backend
- POST https://api.revvly.ai/api/v1/product-events with Authorization: Bearer <API_KEY>, body { "events": [ ... ] }, 1 to 100 events, answered 202 with one result per event (ACCEPTED, DUPLICATE or REJECTED with a reason). One bad event never sinks the others.
- Each event: event (required, up to 128 characters, a stable name like report.exported), userId and/or clientId (at least one, your own ids, up to 255 characters), eventId (optional but send it: the same id again returns DUPLICATE, so retries are safe), timestamp (optional ISO 8601, up to 15 minutes ahead and 30 days back), properties (optional flat object, up to 50 keys; strings up to 500 characters, numbers, booleans or null; no nesting).
- Send ids, not personal data. A property named like a credential, and any value that is an email, a JWT or a card number, is stored as [redacted].
Step 3 — Browser tracking (optional)
- Add one backend route that reads the signed-in person from the operator's own session (never from the request) and calls POST /api/v1/product-events/tokens with { userId, clientId, ttlSeconds }. Return the token string.
- Load the script from the operator's Revvly address, <script src="https://<operator's Revvly address>/revvly-events.js"></script>, then RevvlyEvents.init({ getToken: () => fetch('/their-route').then(r => r.text()) }). There is no package to install or upgrade: the script is served by Revvly and a page gets the current version on each load.
- It records page.viewed and page.left (with active seconds) for you; RevvlyEvents.track('name', { ...flat properties }) adds your own. Ids in paths become :id; the query string, hash and page title are not sent. Offer respectDoNotTrack: true and RevvlyEvents.optOut() / optIn().
Step 4 — Verify
- Settings → Product events lists each new event name under New; an admin names and organises them.
- The client's Usage tab fills in. A 403 on the browser route means the page's origin is not under Allowed sites; a 401 means the token expired or its key was revoked.
Clarifying questions to ask the operator first
1. Which clients' ids the product uses, and whether each is already an External ID on the client record.
2. How the backend identifies the signed-in person and company (session, JWT) for the token route.
3. Whether to send only from the backend or also from the browser, and every origin (production, staging, local) that will send.
4. Which events matter (the features worth knowing a client uses), so names stay stable.
5. Whether the operator's own privacy notice already covers product usage measurement.#How the pieces fit
There is no package to install or upgrade. The server half is one HTTP call. The browser half is a script that Revvly serves from your dashboard address, so a page loads the current version every time and nothing on your side goes out of date. If you copy the file onto your own host instead, you own keeping it current.
#Step 1: Ask Revvly to switch Product usage on
Product usage is an add-on that Revvly switches on for your workspace; it is not a setting you flip yourself. When it is on, a Product events page appears under Settings, Product usage appears in the Clients menu and the key form below gains the tick you need. If you cannot see them, ask Revvly to turn it on.
#Step 2: Issue an API key that can send events
- Settings
- API Keys
- Issue key
- Can send product events
Admins and owners. The tick only appears once Product usage is on, and no existing key has it. The key is shown once: put it in your server's secret store, e.g. REVVLY_PRODUCT_EVENTS_KEY.
#Step 3: Tell Revvly who your clients are
- Clients
- a client
- Edit Client
- External ID
Your own id for that company, unique in your workspace.
An event names the person and the company in your own ids. The company id (clientId) is matched to the client’s External ID, so set each one to what your product already calls that company. An event whose clientId matches no client is still stored, but it shows on no client. Clients you create through the API can carry one from the start; see External ID integration.
#Step 4: Send events from your backend
One call carries 1 to 100 events. Each needs an event name and at least one of userId and clientId. Send an eventId of your own so a retry is recognised and stored once. One bad event is refused on its own and never sinks the others.
// Anywhere on your server, when something worth knowing happens.
// The key lives in your secret store, never in a page or an app bundle.
await fetch('https://api.revvly.ai/api/v1/product-events', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REVVLY_PRODUCT_EVENTS_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
events: [
{
eventId: `export-${report.id}`, // the same id again is DUPLICATE: retries are safe
event: 'report.exported',
userId: user.id, // your own id for the person
clientId: user.accountId, // your own id for their company = the client's External ID
properties: { format: 'pdf' }, // flat values only; send ids, not personal data
},
],
}),
});
// 202 { "results": [ { "eventId": "export-812", "status": "ACCEPTED" } ] }Keep names stable and readable (report.exported, not a sentence with a date in it): each new name becomes a line in Settings, Product events that someone has to organise. Every field and limit is in the Product Events API reference.
#Step 5: Add page views and time on page (optional)
Skip this if server events are enough. The browser half has three parts.
5a. A token route on your backend
One route your signed-in pages call. It reads the person from your own session, never from the request, and asks Revvly for a token for them. The token lasts 15 minutes by default (60 to 3600 seconds); the script asks again before it expires. Revoking the key stops every token it minted.
// server.js (Express). Same key as above; it mints, the page never sees it.
import express from 'express';
const app = express();
// requireSignedInUser is your own auth middleware.
app.get('/api/revvly-token', requireSignedInUser, async (req, res) => {
const user = req.user; // from YOUR session, never from the request
const response = await fetch('https://api.revvly.ai/api/v1/product-events/tokens', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.REVVLY_PRODUCT_EVENTS_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ userId: user.id, clientId: user.accountId, ttlSeconds: 900 }),
});
if (!response.ok) return res.status(502).end();
const { token } = await response.json();
res.type('text/plain').send(token); // the script asks again before it expires
});5b. List the sites that may send
- Settings
- Product events
- Browser tracking
- Allowed sites
Admins only. One entry per line: scheme and host, for example https://app.example.com, plus http://localhost:3000 for development.
This is the second check: a token handed to a page on any other site is refused with a 403. For many subdomains write https://*.example.com: it covers every subdomain at any depth but not example.com itself, another scheme or another port, so list the bare domain on its own line. A white-label domain is always its own line. Up to 100 lines.
5c. Load the script and start it
Use the address you sign in to Revvly at. It records page.viewed on load and on every single-page navigation and page.left with the seconds the page was actually visible. Ids in paths become :id, and the query string, hash and page title are never sent.
<!-- Before </body> on your signed-in pages.
Use the address you sign in to Revvly at (no package to install). -->
<script src="https://<your-revvly-address>/revvly-events.js"></script>
<script>
RevvlyEvents.init({
getToken: () => fetch('/api/revvly-token').then((r) => r.text()),
respectDoNotTrack: true,
});
// Page views and time on each page are recorded for you. Add your own:
RevvlyEvents.track('report.exported', { format: 'pdf' });
</script>Options, privacy controls (optOut, Do Not Track) and delivery behaviour are in the Browser SDK section of the reference. The script keeps a session id in the browser’s local storage, so say so in your own privacy notice and honour your visitors’ choices.
#Step 6: Give the events names
- Settings
- Product events
Admins only. New names wait under New until someone organises them.
Open an event, give it a readable name and a category, and tie it to the feature, module or bundle it shows use of. That mapping is what turns a list of event names into the Feature set card on each client. Leave page.viewed and page.left as they are: they feed the session views. Suggest from documentation proposes mappings from your knowledge base; nothing is applied until you accept it. See See how clients use your product.
#Step 7: Check it works
Trigger something once, then open the client’s Usage tab (Clients, the client, Usage). Events appear within a few seconds; the Feature set card and the overview use a nightly roll-up, so they fill the next day.
#Before you go live
- The key is only on your server: not in the page, logs, URLs or git.
- The token route mints only for the person your own session signed in.
- Every production site is under Allowed sites; the bare domain is listed too.
- Every client you track has an External ID equal to the clientId you send.
- Events carry ids and flat properties, no personal data.
- Your privacy notice says you measure product use, and you honour Do Not Track and an opt-out.
- You know how to erase one person’s events on request (the erase call in the reference).