Revenue API Reference
All revenue events are sent to POST /api/ingest with the following schema. This page documents both the @pulsora/revenue SDK methods and the raw HTTP payload.
SDK: revenue.track(data)
import { Revenue } from '@pulsora/revenue';
const revenue = new Revenue({ apiToken: 'sec_123' });
await revenue.track({
visitor_fingerprint: 'fp_abc',
session_id: 'sess_xyz',
customer_id: 'cus_42',
amount: 499.0,
currency: 'USD',
transaction_id: 'ch_1Nv…',
payment_processor: 'stripe',
event_type: 'purchase',
metadata: {
plan: 'pro',
seats: 10,
},
});
Accepted Fields
| Field | Type | Required | Description |
|---|---|---|---|
visitor_fingerprint |
string |
✅ | Fingerprint from @pulsora/core. |
session_id |
string |
✅ | Session ID from @pulsora/core. |
customer_id |
string |
✅ | Your internal customer identifier. |
amount |
number |
✅ | Gross revenue amount. Positive numbers only. |
currency |
string |
❌ | ISO currency code (default: USD). |
transaction_id |
string |
✅ | Unique transaction reference (payment intent, invoice, etc.). |
payment_processor |
string |
❌ | e.g. stripe, paddle, chargebee. |
event_type |
string |
❌ | One of purchase, subscription, renewal, upgrade, downgrade, churn, refund. |
customer_subscription_id |
string |
❌ | Subscription identifier (if applicable). |
mrr_impact |
number |
❌ | Monthly recurring revenue delta (positive/negative). |
is_recurring |
boolean |
❌ | Set to true for recurring charges. |
metadata |
Record<string, any> |
❌ | Additional context (plan name, coupon, campaign, etc.). |
Validation errors throw descriptive exceptions in the SDK (and return 400 from the API when using raw requests).
Raw HTTP Payload
If you prefer to call the ingest endpoint yourself, send a JSON payload with a secret token:
POST https://pulsora.co/api/ingest
X-API-Token: sec_123
Content-Type: application/json
{
"type": "revenue",
"token": "sec_123",
"data": {
"visitor_fingerprint": "fp_abc",
"session_id": "sess_xyz",
"customer_id": "cus_42",
"amount": 499,
"currency": "USD",
"transaction_id": "ch_1Nv...",
"payment_processor": "stripe",
"event_type": "purchase"
}
}
Responses
| Status | Body | Meaning |
|---|---|---|
202 Accepted |
{ "success": true } |
Event queued for processing successfully. |
400 Bad Request |
{ "message": "...validation..." } |
Missing required fields or invalid types. |
401 Unauthorized |
{ "error": "Invalid API token" } |
Token missing, malformed, or deactivated. |
403 Forbidden |
{ "error": "Revenue events require secret token" } |
Tried to send revenue with a public token. |
429 Too Many Requests |
{ "error": "Rate limited" } + Retry-After header |
Slow down; Pulsora automatically retries with backoff. |
The SDK handles 429 responses with exponential backoff using the maxRetries and retryBackoff configuration.
Event Types
| Event | When to Use |
|---|---|
purchase |
One-off purchases, e.g. lifetime deals, add-ons, or invoices. |
subscription |
New subscription signups (first charge). |
renewal |
Recurring subscription renewals on the same plan. |
upgrade |
Customer moves to a higher plan or adds seats (positive MRR impact). |
downgrade |
Customer moves to a lower plan (negative MRR impact). |
churn |
Subscription cancelled or expired (set mrr_impact negative). |
refund |
Refund issued for a previous payment. |
Each event type feeds different dashboards (MRR, churn, expansion, contraction). For churn/refund, send a negative mrr_impact / amount if needed.
Metadata Conventions
Metadata is optional but extremely powerful. Recommended keys:
plan— Plan or SKU the customer purchased.interval—monthly,annual, etc.coupon— Discount code applied.source— e.g.partner,sales,self-serve.campaign— Marketing campaign for internal alignment.
Metadata is stored in Pulsora and searchable in the revenue explorer.
Error Handling
Wrap track() calls in try/catch to surface issues to your monitoring stack:
try {
await revenue.track(payload);
} catch (error) {
logger.error('Failed to send revenue event', { error, payload });
throw error; // or handle gracefully
}
For more implementation patterns, jump to the revenue examples →