Report usage events to Revvly.
Stream metered usage — API calls, token consumption, seat changes, commissions — directly from your application. Events are validated, deduplicated, and applied to your subscriptions in real time.
Authentication
Every request requires two credentials issued together when you create or rotate an API key. Both are shown once and never again — capture them before closing the dialog.
Authorization: Bearer rvly_sk_live_<your-token> Content-Type: application/json X-Signature-SHA256: sha256=<hmac-hex>
Give this prompt to an integration agent
Copy this into an agent that is building a metering or commission-event integration.
You are implementing signed Revvly event ingestion from a trusted backend that streams individually-identified metered usage or commission events.
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/api/events
Goal
Stream immutable, deduplicated usage events into Revvly so meters aggregate them without your side tracking running totals.
Endpoint and auth
- POST https://api.revvly.ai/api/v1/events — Bearer token AND an HMAC signature. Both are required.
- Authorization: Bearer <API_KEY>
- X-Signature-SHA256: sha256=<hex HMAC-SHA256 of the exact raw request body under the key's signing secret>. Bare hex without the prefix is also accepted.
- Content-Type: application/json
- Serialize the JSON once, HMAC those exact bytes, send those same bytes. Re-serializing after signing (or letting an HTTP client re-encode the body) invalidates the signature.
- The signing secret is a second credential, shown once alongside the token at issue and rotation. A key issued before signing secrets existed has none and this endpoint answers 401 "API key has no signing secret" until the key is rotated.
- There is no Idempotency-Key header here — the per-event eventId is the idempotency key.
Batch shape
{ "events": [ … ] } with 1 to 1000 events. An empty or non-array `events` is 400; more than 1000 is 400.
Per-event fields
- eventId — required. Stable and unique per tenant; ULID recommended. This is the dedup key: a replay returns DUPLICATE and applies nothing.
- Exactly one subscription reference: subscriptionId OR externalSubscriptionId (the SUBSCRIPTION's externalId, not the client's)
- Exactly one meter reference: variableId OR variableSlug
- type — USAGE_REPORTED (the default when omitted), USAGE_CORRECTED, or COMMISSION_EVENT
- quantity — required for USAGE_REPORTED / USAGE_CORRECTED
- saleAmount + transactionId — required for COMMISSION_EVENT
- reportedAt — required ISO 8601. No more than 15 minutes in the future and no more than 7 days in the past, or the event is REJECTED for clock skew. Backfills older than 7 days need an operator, not a retry.
- context — optional free-form object carried into the audit trail
Reading the response
HTTP 202 means the batch was accepted for processing — it is NOT a per-event success. Inspect every results[] entry; they come back in the order you sent them.
- ACCEPTED — validated and queued; this is the normal success status
- PROCESSED — applied synchronously (returned when the queue is bypassed)
- DUPLICATE — this eventId was already seen; the original outcome stands. Safe, and not an error.
- REJECTED — rejectionReason carries the cause (unresolved reference, missing field, clock skew, scope violation). Surface it; replaying the same payload will not fix it.
One bad event never sinks the batch — the rest still apply.
Discover references
- GET https://api.revvly.ai/api/v1/variables — meter ids and slugs
- GET https://api.revvly.ai/api/v1/subscriptions and /subscriptions/by-external/:externalId — subscription references
- Discovery reads are Bearer-only; no HMAC on reads.
Choosing this endpoint over /api/v1/usage
- /api/v1/events when the source emits discrete events with their own ids, and you want dedup on eventId plus an append-only trail.
- /api/v1/usage* when you hold the delta or the absolute count and want a synchronous answer with the resulting totals.
- Never send the same consumption through both.
Example
POST https://api.revvly.ai/api/v1/events
Authorization: Bearer <API_KEY>
X-Signature-SHA256: sha256=<hmac-hex>
Content-Type: application/json
{
"events": [
{
"eventId": "01HYZ7K2N8V3WXPQ4JCBM5FGTE",
"externalSubscriptionId": "sub-acme-growth-2026",
"variableSlug": "api-calls",
"type": "USAGE_REPORTED",
"quantity": 1500,
"reportedAt": "2026-08-02T14:00:00.000Z"
}
]
}
Implementation checklist
1. Confirm where eventId comes from (a provider event id, or a ULID you mint and persist before sending).
2. Confirm where the signing secret lives and that it rotates together with the Bearer token.
3. Sign raw bytes — assert in a test that the buffer you signed is the buffer you wrote to the socket.
4. Persist per-event outcomes so REJECTED rows can be repaired without replaying ACCEPTED ones.
5. Add tests for happy path, duplicate eventId, a clock-skew rejection, and one bad reference inside an otherwise-valid batch.#Endpoint
Submit a batch of up to 1 000 events per request. Each event is processed independently — one rejected event does not block the others. The response always returns one result object per submitted event.
Request body
Shape{
"events": [Event, ...] // 1 – 1 000 items
}Event fields
USAGE_REPORTED — add or remove usage (sign of quantity controls direction).
USAGE_CORRECTED — the same application path, tagged as a correction in the audit trail. Also needs quantity.
COMMISSION_EVENT — record a sale for revenue-share billing.
Where do these IDs come from?
You don’t have to store Revvly’s internal Mongo ids at all. The recommended pattern is to set externalId on the subscription at creation — your own account or subscription id — and then always report usage with externalSubscriptionId. Discover a meter’s variableSlug (or its internal variableId) once via GET https://api.revvly.ai/api/v1/variables. If you’d rather sync and store Revvly ids, list subscriptions with GET https://api.revvly.ai/api/v1/subscriptions. Both the pattern and the discovery endpoints are covered in full on the External references & ID discovery page.
Signing the request
Compute HMAC-SHA256 over the exact bytes you send as the request body, using the signing secret as the key. Pass the result as a hex string in the X-Signature-SHA256 header.
const crypto = require('crypto');
function signRequest(body, signingSecret) {
return crypto
.createHmac('sha256', signingSecret)
.update(body) // raw bytes — do not re-serialize
.digest('hex');
}Full examples
A complete request — authentication, signature, and a single USAGE_REPORTED event.
const crypto = require('crypto');
const https = require('https');
const API_KEY = 'rvly_sk_live_<your-token>';
const SIGN_SECRET = '<your-signing-secret>';
const HOST = 'api.revvly.ai';
const payload = JSON.stringify({
events: [
{
eventId: '01HYZ7K2N8V3WXPQ4JCBM5FGTE', // ULID recommended
subscriptionId: '663b1f2e4a3c2d1e0f8b7a9c',
variableId: '663b1f2e4a3c2d1e0f8b7b01',
type: 'USAGE_REPORTED',
quantity: 1,
reportedAt: new Date().toISOString(),
},
],
});
const sig = crypto
.createHmac('sha256', SIGN_SECRET)
.update(payload)
.digest('hex');
const req = https.request({
host: HOST,
path: '/api/v1/events',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
'Authorization': `Bearer ${API_KEY}`,
'X-Signature-SHA256': `sha256=${sig}`,
},
}, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => console.log(JSON.parse(data)));
});
req.write(payload);
req.end();Response
Revvly returns one result object per submitted event, in submission order. A 202 Accepted status is returned even when individual events are rejected — inspect each result’s status field.
HTTP/1.1 202 Accepted
{
"results": [
{
"eventId": "01HYZ7K2N8V3WXPQ4JCBM5FGTE",
"status": "PROCESSED",
"currentCount": 42
},
{
"eventId": "01HYZ7K2N8V3WXPQ4JCBM5FGTF",
"status": "DUPLICATE"
},
{
"eventId": "01HYZ7K2N8V3WXPQ4JCBM5FGTG",
"status": "REJECTED",
"rejectionReason": "reportedAt is more than 7 days in the past"
}
]
}- Signed requestBearer key plus HMAC of the raw body.
- Batch acceptedUp to 1,000 events. More returns 400.
- PROCESSEDEvent applied to the subscription.orDUPLICATESame eventId already received. No delta.orREJECTEDFailed validation or scope. A reason is given.
- 202 AcceptedOne result per event, in submission order.
Validation rules
Key rotation
Rotate an API key from the dashboard or via the rotateApiKey GraphQL mutation. After rotation, both the old and new key remain valid for 30 days, giving you time to deploy the replacement signing secret without downtime.