Generic revenue API
Send revenue commands through the generic API.
Send payments and refunds from any processor FounderHQ has no connection for. One request per money event.
Before you start
You need a secret key (fhq_sk_XXXX) that is scoped
to one brand and may write events. A publishable key is rejected. A key with no
brand is rejected.
Endpoint
POST https://i.getfounderhq.com/api/revenue
authorization: Bearer fhq_sk_XXXX
content-type: application/jsonFounderHQ answers 202 as soon as the command is stored. Normalization,
matching, and attribution happen after that, so a slow ledger never slows your
checkout.
Using the Node SDK? events.captureRevenue(command) posts the same body and
retries for you.
Example
curl -X POST https://i.getfounderhq.com/api/revenue \
-H "authorization: Bearer fhq_sk_XXXX" \
-H "content-type: application/json" \
-d '{
"idempotencyKey": "acme:payment:pay_ABC123",
"transactionId": "pay_ABC123",
"transactionRefType": "payment_intent",
"kind": "payment",
"amountMinor": "4900",
"currency": "USD",
"occurredAt": "2026-05-23T09:15:00.000Z",
"paymentRail": "razorpay",
"environment": "live",
"customerEmail": "jane@acme.com",
"checkoutVisitorId": "6b1f0d64-1f0e-4e5a-9a2a-6b0f1d8c2e77",
"providerCustomerId": "cust_42",
"providerSubscriptionId": "sub_42",
"subscription": {
"status": "active",
"interval": "P1M",
"currentPeriodEnd": "2026-06-23T09:15:00.000Z",
"plan": "growth"
}
}'Response
{
"ok": true,
"deliveryId": "DELIVERY_ID",
"status": "QUEUED",
"created": true
}| Field | Meaning |
|---|---|
ok | Always true on a 202. |
deliveryId | FounderHQ's ID for this command. Log it. |
status | QUEUED, PROCESSING, PROCESSED, or FAILED. |
created | true for a new command. false when you replayed one FounderHQ already had. |
Idempotency
idempotencyKey identifies the delivery. Reuse the same key on every retry of
the same money event.
- Same key, same body: FounderHQ returns
202withcreated: false. Nothing is counted twice. - Same key, different body: FounderHQ returns
409. Pick a new key, or send the body you sent the first time. - Different key, same
transactionId,transactionRefType, andkind: FounderHQ still counts the money once. The transaction is what dedupes the economics.
Keys are scoped to the API key you send them with.
Fields
| Field | Type | Required | Meaning |
|---|---|---|---|
idempotencyKey | string, 1–512 | yes | Stable ID for this delivery. Retries reuse it. |
transactionId | string, 1–512 | yes | The processor's transaction ID. FounderHQ dedupes the money by it. |
transactionRefType | enum | no, default transaction | What transactionId points at: payment_intent, charge, invoice_payment, refund, transaction, order, dispute, credit_note. |
kind | enum | no, default payment | payment, refund, dispute_lost, or credit_note. |
amountMinor | integer, string or number | yes | Amount in minor units. 4900 is $49.00. Never 0. Signed or unsigned: FounderHQ normalizes the sign from kind. |
currency | 3 letters | yes | The charged currency, for example USD. |
taxMinor | integer | no | Tax inside the amount. |
feeMinor | integer | no | The processor's fee. |
settlementAmountMinor | integer | no | What actually reached your bank. |
settlementCurrency | 3 letters | no | The currency of that settlement. |
reportingCurrency | 3 letters | no | The currency you want this payment reported in. |
fxRate | decimal, string or number | no | The rate used to reach the reporting currency. |
occurredAt | ISO 8601 datetime | no | When the money moved. Without it, FounderHQ uses the time it received the command, and marks the date as a fallback. Send it. |
providerRevisionAt | ISO 8601 datetime | no | When the processor last revised this record. FounderHQ uses it to decide which correction is newer. |
providerRevisionKey | string, 1–512 | no | Your ID for that revision. Defaults to the idempotency key. |
paymentRail | string, 1–64 | no, default generic | The processor this money moved on, for example razorpay. |
merchantAccountId | string, 1–256 | no | Your merchant or account ID with that processor. |
environment | string, 1–32 | no, default live | The environment this money moved in. FounderHQ stores it on the payment, so test money stays labelled as test. |
originalTransactionId | string, 1–512 | required for reversals | The payment this refund, lost dispute, or credit note cancels. |
originalRefType | enum | no, default transaction | What originalTransactionId points at: payment_intent, charge, invoice_payment, transaction, order. |
checkoutVisitorId | string, 1–400 | no | The fhq_anonymous_id your checkout captured. This is what ties the payment to a channel. |
checkoutAccountContext | string, 1–2048 | no | The fhq_account_context token, when the buyer paid on behalf of an account. |
providerCustomerId | string, 1–512 | no | The processor's customer ID. It binds renewals to the same contact. |
providerSubscriptionId | string, 1–512 | no | The processor's subscription ID. Required when you send subscription. |
customerEmail | no | The payer's email. FounderHQ uses it to find the contact when no visitor ID is present. | |
historicalImport | boolean | no, default false | Marks a backfilled payment. The amount counts, but with no touch it stays unmatched instead of counting as direct traffic. |
metadata | object | no | Your own keys. Stored with the payment. |
attributionWindowDays | 30, 60, 90, or 180 | no, default 90 | How far back FounderHQ looks for the touches that earn this payment. |
subscription | object | no | Recurring state. See below. |
subscription
Send this block on a recurring payment and FounderHQ drives MRR from it, the same way a connected provider does.
| Field | Type | Required | Meaning |
|---|---|---|---|
status | enum | no, default active | trialing, active, past_due, paused, canceled, or expired. |
priceMinor | integer | no | The plan price in minor units. Defaults to amountMinor on a payment. |
interval | ISO 8601 period, or null | no | P1M for monthly, P1Y for yearly. Without it, MRR is 0 and the period stays unknown. |
currentPeriodEnd | ISO 8601 datetime, or null | no | When this paid period ends. |
quantity | integer, 1–10000 | no, default 1 | Seats or units. |
plan | string, 1–200, or null | no | Your plan name, for example growth. |
Rules FounderHQ enforces
| Rule | Why |
|---|---|
amountMinor must not be 0 | A zero amount is not a money event. |
A refund, lost dispute, or credit note must carry originalTransactionId | A reversal has to say what it reverses. |
A payment must not carry originalTransactionId | A payment reverses nothing. |
subscription requires providerSubscriptionId | MRR needs a subscription to attach to. |
Errors
| Status | Meaning |
|---|---|
400 | The body failed a rule. The message names the first field that failed. |
401 | The key is missing, wrong, or not a secret key. |
403 | The key is not scoped to a brand, or cannot write events. |
409 | This idempotency key already exists with a different body. |
Verify
- Send a test payment with
"environment": "test". - Confirm the response is
202with"created": true. - Send the same body again. The response says
"created": false. - In FounderHQ, open Revenue. The payment shows your amount, and the channel
that brought the customer when you sent
checkoutVisitorId.