Transactions
Record what money actually moved — payments, refunds, chargebacks and their reversals.
Transactions
PUT https://api.faurya.com/api/v1/billing/transactions/{external_id}
GET https://api.faurya.com/api/v1/billing/transactions/{external_id}Requires Bearer Token authentication with your Faurya API key.
An invoice says what was billed. A transaction says what money moved against it. It is what makes an invoice paid, what makes your revenue reports show cash — and, for a subscription's first period, what proves service actually commenced.
Every transaction belongs to an invoice, so send the invoice first.
Record a transaction
const response = await fetch(
"https://api.faurya.com/api/v1/billing/transactions/txn_7781",
{
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.FAURYA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
invoice_external_id: "inv_1042",
type: "payment",
result: "succeeded",
amount: 49,
occurred_at: "2026-03-01T09:14:00Z",
fee: 1.72,
metadata: {
provider: "razorpay",
method: "upi",
},
}),
},
);Request body
Required
invoice_external_id(string): The invoice this money moved against. Must already exist.type(string):payment,refund,chargebackorchargeback_reversal.amount(number): Always positive. The type carries the direction, so a refund istype: "refund"withamount: 10, never a payment of-10.
Optional
result(string):succeeded(the default),failedorpending.currency(string): The invoice's currency. You may send it to be checked; a mismatch is refused withCURRENCY_MISMATCH. Omit it and the invoice's currency is used.occurred_at(string): When the money moved, ISO 8601. Defaults to the time Faurya receives the request. Use it when reporting past transactions; it may not be more than 5 minutes in the future.original_transaction_external_id(string): The payment this reverses. Required onrefund,chargebackandchargeback_reversal; not allowed on apayment.fee(number): The processing fee you paid. Defaults to0.fee_currency(string): The currency offee, if it differs from the invoice's.settlement_amountandsettlement_currency: What actually landed in your account, when your processor settles in another currency. The two are one fact — send both or neither.metadata(object): Up to 10 entries. Keys 1–64 characters; values a string (≤500 characters), number, boolean ornull. Objects and arrays are refused.
Decimal places are judged against the invoice's currency, not a guess: 12.50 is fine on a USD invoice and two decimals too many on a JPY one.
In a bulk import a transaction's kind
is sent as transaction_type, because type there names the item kind. It is
the only field whose name differs between the two doors.
Reversals
The three reversal types must name the payment they act on:
| Type | What it means | Names |
|---|---|---|
refund | You gave money back | the payment being refunded |
chargeback | The bank pulled money back | the payment that was pulled back |
chargeback_reversal | You won the dispute | the payment whose chargeback was undone |
The payment they name must exist, have succeeded, and belong to the same invoice — otherwise 422 TRANSACTION_NOT_FOUND.
await fetch("https://api.faurya.com/api/v1/billing/transactions/txn_7782", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.FAURYA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
invoice_external_id: "inv_1042",
type: "refund",
amount: 20,
original_transaction_external_id: "txn_7781",
occurred_at: "2026-03-09T11:00:00Z",
}),
});Faurya marks a refund for less than the full payment as is_partial_refund: true in the response.
Money cannot exceed what it acts on
| Rule | Code when broken |
|---|---|
| Payments on an invoice may not exceed the invoice total | 422 AMOUNT_EXCEEDS_INVOICE |
| Refunds and chargebacks may not exceed the payment they reverse | 422 AMOUNT_EXCEEDS_PAYMENT |
| Reversals may not give back more than was taken | 422 AMOUNT_EXCEEDS_PAYMENT |
Only succeeded money is bounded. A failed or pending transaction moved nothing, so it is accepted whatever its size — and is checked again if it later succeeds.
A transaction is almost entirely immutable
Money that moved is a fact about the past. Re-sending the same transaction is a replay and writes nothing; changing it is refused.
| Field | Can it change? |
|---|---|
type, amount, currency, invoice_external_id, original_transaction_external_id | No — 409 TRANSACTION_IMMUTABLE |
result | Only off pending — to succeeded or failed |
occurred_at | Keeps the stored instant unless you state a new one |
fee, fee_currency, settlement_amount, settlement_currency, metadata | Yes, freely — they describe the same movement |
So the normal lifecycle of a pending charge is: record it as pending, then PUT the same id again with result: "succeeded" and the fee your processor reported.
A transaction id and a payments API
transaction_id share one space. Reusing an id already recorded through
/payments is refused with 409 TRANSACTION_EXISTS_IN_PAYMENTS_API.
Read a transaction
const response = await fetch(
"https://api.faurya.com/api/v1/billing/transactions/txn_7781",
{ headers: { Authorization: `Bearer ${process.env.FAURYA_API_KEY}` } },
);{
"object": "transaction",
"external_id": "txn_7781",
"data": {
"external_id": "txn_7781",
"invoice_external_id": "inv_1042",
"type": "payment",
"result": "succeeded",
"amount": 49,
"currency": "USD",
"occurred_at": "2026-03-01T09:14:00.000Z",
"original_transaction_external_id": null,
"fee": 1.72,
"fee_currency": "USD",
"settlement_amount": null,
"settlement_currency": null,
"is_partial_refund": null,
"metadata": { "provider": "razorpay", "method": "upi" }
},
"request_id": "req_01JQ8Z0Y5N6W3K2M4P7R9T1V3X"
}amount always reads back positive. is_partial_refund is null on anything that is not a refund. result reads back null if the invoice was voided — voiding the document voids its cash with it.
Response codes
200 OK— transaction updated, or the request matched what was stored.201 Created— the transaction did not exist before.400 Bad Request— invalid fields, an unknown field, or a field not allowed for the type.401 Unauthorized— missing or invalid API key.404 Not Found— (GETonly) no transaction with that id.409 Conflict—TRANSACTION_IMMUTABLE,INVOICE_VOIDED,TRANSACTION_EXISTS_IN_PAYMENTS_API, or a concurrent write.422 Unprocessable Entity—INVOICE_NOT_FOUND,TRANSACTION_NOT_FOUND,CURRENCY_MISMATCH,AMOUNT_EXCEEDS_INVOICEorAMOUNT_EXCEEDS_PAYMENT.429 Too Many Requests— over the rate limit.