Skip to documentation content

Stripe payment reports API

Record actual Stripe payment observations with stable identities, cumulative amounts, and deduplicated delivery.

On this page

Report an observed payment

Report an observation · payments:write
POST /api/v1/stripe/payment-reports

Submit 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

FieldRequiredMeaning
eventIdYesStable identity of one immutable observation; retries keep the same value and body.
attemptIdYesStable payment-attempt identity reused by later observations.
paymentIntentId / chargeId / stripeAccountIdNoActual Stripe references when known; they do not authorize another context.
statusYesNormalized status defined below.
currencyYesExactly three lowercase letters.
amountMinor / capturedAmountMinor / refundedAmountMinorYesNonnegative integer minor units using coherent cumulative totals.
occurredAtYesTimezone-aware actual observation time, not delivery retry time.
enrichmentRequestIdNopreparation.id for linked source, request ID for inline when available, or omitted for reporting only.
invoiceReference / errorCode / providerEventIdNoAllowlisted factual references and sanitized codes when known.
sourceYessdk, 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

StatusMeaning
requires_actionCustomer action is required; no capture or refund amount is present.
authorizedAuthorization was observed but capture was not reported.
capturedPositive capture was observed with zero refund.
partially_refundedA positive refund smaller than cumulative capture was observed.
refundedThe positive cumulative capture was fully refunded.
failedA definitive provider failure was observed.
canceledCancellation was observed.
unknownThe 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.

Captured observation
{
  "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

Successful acknowledgment
{
  "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 modeReport enrichmentRequestId
Linked QuickBooks invoiceSaved preparation.id, stable across accepted identical preparation retries.
Inline CanonicalInvoiceThe preparation response x-carden-request-id when available.
Reporting onlyOmit 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

Report-only delivery
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.