Revenue API
Push customer, charge, and subscription data from any payment provider into Himetrica. Manual revenue data integrates seamlessly with all existing revenue metrics — MRR, timeline, cohorts, top customers, and visitor linking.
Overview
The Revenue API lets you track revenue from payment providers that Himetrica doesn't natively integrate with (beyond Stripe, Shopify, and AbacatePay). Use it for:
- Custom payment gateways or in-house billing
- Offline or invoice-based payments
- Marketplace payouts and platform fees
- Webhook-driven revenue ingestion from any source
Base URL: https://app.himetrica.com/api/v1
How it works
Authentication
All Revenue API endpoints require a secret key passed in the X-API-Key header. Secret keys start with hm_sk_ and are the same keys used by the Server API.
Keep your secret key safe
Rate Limiting
All Revenue API endpoints share a single rate limit:
| Scope | Limit | Window |
|---|---|---|
All /revenue/* endpoints | 500 requests | 1 minute |
Rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) are included in every response.
Customers
POST/revenue/customers
Create a new customer. If a visitor with the same email exists in the project, they are automatically linked.
Request Body
email (required) — Customer email
name (optional) — Customer name
currency (optional) — Default currency (e.g. usd, eur). Defaults to usd.
externalId (optional) — Your system's customer ID
metadata (optional) — Arbitrary key-value pairs
curl -X POST "https://app.himetrica.com/api/v1/revenue/customers" \
-H "X-API-Key: hm_sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"name": "Jane Doe",
"currency": "usd"
}'Response 201
{
"id": "a1b2c3d4-...",
"externalCustomerId": "manual_f8e2a1b3c4d5",
"email": "jane@example.com",
"name": "Jane Doe",
"currency": "usd",
"totalRevenue": 0,
"totalRevenueUsd": 0,
"activeSubscriptions": 0,
"subscriptions": [],
"createdAt": "2025-03-19T12:00:00.000Z",
"updatedAt": "2025-03-19T12:00:00.000Z"
}GET/revenue/customers
List manual customers with pagination and optional search.
Query Parameters
page — Page number (default: 1)
limit — Items per page (default: 50, max: 100)
search — Filter by email or name
curl "https://app.himetrica.com/api/v1/revenue/customers?page=1&limit=20&search=jane" \
-H "X-API-Key: hm_sk_your_secret_key"GET/revenue/customers/:id
Get a single customer with their subscriptions and charge history.
curl "https://app.himetrica.com/api/v1/revenue/customers/{id}" \
-H "X-API-Key: hm_sk_your_secret_key"PUT/revenue/customers/:id
Update a customer's fields. Only include the fields you want to change.
curl -X PUT "https://app.himetrica.com/api/v1/revenue/customers/{id}" \
-H "X-API-Key: hm_sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"metadata": { "plan": "enterprise" }
}'DELETE/revenue/customers/:id
Delete a customer and all their associated charges. This action is irreversible.
curl -X DELETE "https://app.himetrica.com/api/v1/revenue/customers/{id}" \
-H "X-API-Key: hm_sk_your_secret_key"Charges
Charges represent one-time payments or invoice line items. They update the customer's total revenue and appear in the revenue timeline chart.
POST/revenue/customers/:id/charges
Add a charge to a customer. The customer's total revenue is automatically recalculated.
Request Body
amount (required) — Charge amount in the smallest currency unit (e.g. cents)
currency (optional) — ISO currency code. Defaults to the customer's currency.
status (optional) — succeeded (default), pending, failed, refunded
description (optional) — Human-readable description
paymentMethod (optional) — e.g. card, bank_transfer, pix
cardBrand (optional) — e.g. visa, mastercard
chargeDate (optional) — ISO 8601 timestamp. Defaults to now.
externalId (optional) — Your system's charge/invoice ID. Used for idempotency.
curl -X POST "https://app.himetrica.com/api/v1/revenue/customers/{id}/charges" \
-H "X-API-Key: hm_sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"amount": 9900,
"currency": "usd",
"description": "Pro Plan - March 2025",
"externalId": "inv_abc123"
}'Response 201
{
"externalId": "inv_abc123",
"amount": 9900,
"amountUsd": 9900,
"currency": "usd",
"status": "succeeded",
"description": "Pro Plan - March 2025",
"createdAt": "2025-03-19T12:00:00.000Z"
}DELETE/revenue/charges/:id
Delete a charge. The customer's total revenue is automatically recalculated.
curl -X DELETE "https://app.himetrica.com/api/v1/revenue/charges/{chargeId}" \
-H "X-API-Key: hm_sk_your_secret_key"Subscriptions
Subscriptions represent recurring revenue and are used to calculate MRR. They are stored on the customer object and update the MRR charts in your dashboard.
POST/revenue/customers/:id/subscriptions
Add a subscription to a customer.
Request Body
productName (required) — Name of the plan/product
amount (required) — Recurring amount per interval (in smallest currency unit)
currency (optional) — ISO currency code. Defaults to customer's currency.
interval (optional) — month (default) or year
status (optional) — active (default), trialing, canceled, past_due
externalId (optional) — Your system's subscription ID
currentPeriodStart / currentPeriodEnd (optional) — ISO 8601 dates
canceledAt (optional) — When the subscription was canceled
trialStart / trialEnd (optional) — Trial period dates
curl -X POST "https://app.himetrica.com/api/v1/revenue/customers/{id}/subscriptions" \
-H "X-API-Key: hm_sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"productName": "Pro Plan",
"amount": 2900,
"currency": "usd",
"interval": "month",
"status": "active"
}'Response 201
{
"externalId": "manual_sub_d4e5f6a7b8c9",
"productName": "Pro Plan",
"status": "active",
"amount": 2900,
"amountUsd": 2900,
"currency": "usd",
"interval": "month",
"createdAt": "2025-03-19T12:00:00.000Z"
}PUT/revenue/customers/:id/subscriptions/:subId
Update a subscription. Use this to change the plan, cancel, or update the status. The subId is the subscription's externalId.
curl -X PUT "https://app.himetrica.com/api/v1/revenue/customers/{id}/subscriptions/{subId}" \
-H "X-API-Key: hm_sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"status": "canceled",
"canceledAt": "2025-03-19T00:00:00.000Z"
}'DELETE/revenue/customers/:id/subscriptions/:subId
Remove a subscription entirely. If you want to mark it as canceled instead, use the PUT endpoint with status: "canceled".
curl -X DELETE "https://app.himetrica.com/api/v1/revenue/customers/{id}/subscriptions/{subId}" \
-H "X-API-Key: hm_sk_your_secret_key"Idempotency
When you provide an externalId on a charge, the API uses a unique constraint to prevent duplicates. If a charge with the same externalId already exists, the existing record is updated instead of creating a duplicate.
This makes it safe to retry webhook deliveries or re-run import scripts without worrying about double-counting revenue.
Best practice
externalId when recording charges. If omitted, a random ID is generated and idempotency is not enforced.Currency Conversion
All amounts are stored in the original currency and automatically converted to USD using live exchange rates (updated hourly). The USD equivalent is used for aggregated metrics like MRR, total revenue, and top customers.
Supported currencies include all major ISO 4217 codes (EUR, GBP, BRL, JPY, etc.).
Error Handling
All errors return a JSON object with an error field.
| Status | Meaning |
|---|---|
400 | Bad request — missing or invalid parameters |
401 | Unauthorized — missing or invalid secret key |
404 | Customer, charge, or subscription not found |
429 | Rate limited — too many requests |
500 | Internal server error |
503 | Service temporarily unavailable |
Examples
Node.js / TypeScript
const API_URL = "https://app.himetrica.com/api/v1";
const SECRET_KEY = process.env.HIMETRICA_SECRET_KEY; // hm_sk_...
// Create a customer
const customer = await fetch(`${API_URL}/revenue/customers`, {
method: "POST",
headers: {
"X-API-Key": SECRET_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "jane@example.com",
name: "Jane Doe",
currency: "usd",
}),
}).then(r => r.json());
// Add a charge
await fetch(`${API_URL}/revenue/customers/${customer.id}/charges`, {
method: "POST",
headers: {
"X-API-Key": SECRET_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 9900,
currency: "usd",
description: "Pro Plan - March 2025",
externalId: "inv_abc123", // prevents duplicates
}),
});
// Add a subscription
await fetch(`${API_URL}/revenue/customers/${customer.id}/subscriptions`, {
method: "POST",
headers: {
"X-API-Key": SECRET_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
productName: "Pro Plan",
amount: 2900,
interval: "month",
}),
});Python
import requests
import os
API_URL = "https://app.himetrica.com/api/v1"
SECRET_KEY = os.environ["HIMETRICA_SECRET_KEY"] # hm_sk_...
HEADERS = {"X-API-Key": SECRET_KEY, "Content-Type": "application/json"}
# Create a customer
customer = requests.post(
f"{API_URL}/revenue/customers",
headers=HEADERS,
json={"email": "jane@example.com", "name": "Jane Doe", "currency": "usd"},
).json()
# Add a charge
requests.post(
f"{API_URL}/revenue/customers/{customer['id']}/charges",
headers=HEADERS,
json={
"amount": 9900,
"currency": "usd",
"description": "Pro Plan - March 2025",
"externalId": "inv_abc123",
},
)
# Add a subscription
requests.post(
f"{API_URL}/revenue/customers/{customer['id']}/subscriptions",
headers=HEADERS,
json={"productName": "Pro Plan", "amount": 2900, "interval": "month"},
)Webhook Handler (Express)
A common pattern is to use the Revenue API inside webhook handlers from your payment provider. This example shows how to record payments from a custom gateway:
// Example: Webhook handler for a custom payment provider
app.post("/webhooks/payments", async (req, res) => {
const event = req.body;
if (event.type === "payment.succeeded") {
// Find or create customer in Himetrica
let customer;
try {
const list = await fetch(
`${API_URL}/revenue/customers?search=${encodeURIComponent(event.customer_email)}`,
{ headers: { "X-API-Key": SECRET_KEY } }
).then(r => r.json());
customer = list.customers[0];
} catch {}
if (!customer) {
customer = await fetch(`${API_URL}/revenue/customers`, {
method: "POST",
headers: { "X-API-Key": SECRET_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
email: event.customer_email,
name: event.customer_name,
externalId: event.customer_id,
}),
}).then(r => r.json());
}
// Record the charge (externalId prevents duplicates on retries)
await fetch(`${API_URL}/revenue/customers/${customer.id}/charges`, {
method: "POST",
headers: { "X-API-Key": SECRET_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
amount: event.amount,
currency: event.currency,
externalId: event.payment_id,
description: event.description,
chargeDate: event.created_at,
}),
});
}
res.json({ received: true });
});