Stripe payment reports API
Record actual Stripe payment observations with stable identities, cumulative amounts, and deduplicated delivery.
On this page
Report an observed payment
POST /api/v1/stripe/payment-reportsSubmit an actual attempt or later state observed by the merchant backend. Authorization fixes merchant, Stripe integration, and environment. The endpoint records the report and never executes or retries a payment.
Preparation is optional. A reporting-only key needs payments:write and no source connection. For linked QuickBooks preparation, use saved preparation.id as enrichmentRequestId even after later source changes.
Request fields
| Field | Required | Meaning |
|---|---|---|
| eventId | Yes | Stable identity of one immutable observation; retries keep the same value and body. |
| attemptId | Yes | Stable payment-attempt identity reused by later observations. |
| paymentIntentId / chargeId / stripeAccountId | No | Actual Stripe references when known; they do not authorize another context. |
| status | Yes | Normalized status defined below. |
| currency | Yes | Exactly three lowercase letters. |
| amountMinor / capturedAmountMinor / refundedAmountMinor | Yes | Nonnegative integer minor units using coherent cumulative totals. |
| occurredAt | Yes | Timezone-aware actual observation time, not delivery retry time. |
| enrichmentRequestId | No | preparation.id for linked source, request ID for inline when available, or omitted for reporting only. |
| invoiceReference / errorCode / providerEventId | No | Allowlisted factual references and sanitized codes when known. |
| source | Yes | sdk, merchant_webhook, or api; this describes the reporting path, not provider verification. |
Report objects are strict. Raw Stripe objects, extra metadata, credentials, and card data are rejected. Optional references are omitted when unknown. Keep amount and currency facts accurate even when they differ from a preparation; Carden records the mismatch instead of rewriting the payment.
Use normalized statuses
| Status | Meaning |
|---|---|
| requires_action | Customer action is required; no capture or refund amount is present. |
| authorized | Authorization was observed but capture was not reported. |
| captured | Positive capture was observed with zero refund. |
| partially_refunded | A positive refund smaller than cumulative capture was observed. |
| refunded | The positive cumulative capture was fully refunded. |
| failed | A definitive provider failure was observed. |
| canceled | Cancellation was observed. |
| unknown | The provider result is unresolved; it is neither success nor failure. |
Captured amount cannot exceed amount, and refunded amount cannot exceed capture. requires_action, authorized, failed, and canceled use zero capture/refund. unknown may retain previously established cumulative amounts while reconciliation continues.
Example request
The identifiers and timestamp are synthetic. Replace them with stable identities and the actual occurrence time stored by the merchant backend.
{
"eventId": "report_attempt_1042_captured_1",
"attemptId": "attempt_1042_1",
"paymentIntentId": "pi_example",
"chargeId": "ch_example",
"status": "captured",
"currency": "usd",
"amountMinor": 11300,
"capturedAmountMinor": 11300,
"refundedAmountMinor": 0,
"occurredAt": "2026-01-01T12:00:00.000Z",
"invoiceReference": "INV-1042",
"source": "api"
}A later refund uses the same attemptId and amount, a new eventId, the actual event time, and cumulative captured and refunded totals.
Acknowledge and deduplicate
{
"object": "stripe.payment_report",
"api_version": "v1",
"data": {
"id": "report_example",
"duplicate": false
}
}Repeated identical delivery in the same organization, integration, and environment returns duplicate: true. Treat either duplicate value as acknowledgment. Preserve the receipt and response request ID in the outbox record.
A 409 indicates that an accepted event ID was reused with different payload, stable payment aliases conflict, or cumulative totals or observation time move backward. Reconcile identity and facts instead of retrying blindly. Accepted observations remain immutable; current payment state is a projection that never deletes report history.
Correlate without changing payment facts
| Preparation mode | Report enrichmentRequestId |
|---|---|
| Linked QuickBooks invoice | Saved preparation.id, stable across accepted identical preparation retries. |
| Inline CanonicalInvoice | The preparation response x-carden-request-id when available. |
| Reporting only | Omit the field. |
Use the preparation attached to the attempt, never a fresh invoice lookup during report delivery. Source edits or disconnects leave historical evidence intact. Amount or currency mismatches remain actual observations and create correlation exceptions.
Deliver from a durable worker
const response = await fetch(new URL("/api/v1/stripe/payment-reports", baseUrl), {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(report),
signal: AbortSignal.timeout(5000),
});
const requestId = response.headers.get("x-carden-request-id");
const payload = await response.json();
if (!response.ok) {
throw new Error(`Carden report failed: ${response.status} (${requestId})`);
}
if (payload?.object !== "stripe.payment_report" ||
payload.api_version !== "v1" ||
typeof payload.data?.id !== "string" ||
typeof payload.data?.duplicate !== "boolean") {
throw new Error("Unexpected Carden report response");
}
await reportOutbox.markDelivered(report.eventId, {
reportId: payload.data.id,
requestId,
});The worker must catch transport failures, retain the immutable outbox row, and classify permanent versus transient responses. Never route report delivery failure into the Stripe payment retry path.