Build an enrichment integration
The recommended enrichment pattern: map Chargeflow and processor dispute IDs, read each dispute with expand, then send only the fields Chargeflow is missing.
You are wiring your systems to send dispute data to Chargeflow, and you want a pattern that holds up in production. The endpoints are simple; the part integrators get stuck on is the order of operations. This guide is that order: map the IDs, read before you write, enrich only the gaps.
Before you build, run a gap analysis: connect your processor and billing platform first, so you only build enrichment for data Chargeflow cannot already pull.
The flow
Before you start
- An API key in the
CHARGEFLOW_API_KEYenvironment variable. See Authentication. - A
dispute.createdwebhook registered to your endpoint. See Subscribe to webhook events. - The difference between the Chargeflow dispute ID and the processor's
source_id. See Dispute identifiers.
Steps
Build the ID mapping
Every API call takes the Chargeflow dispute ID, but your systems know the processor's ID. Store both as each dispute arrives.
From the webhook. On dispute.created, upsert data.id, data.source, and data.source_id, then reply 200:
app.post('/webhooks/dispute-created', express.json(), async (req, res) => {
const { id, source, source_id } = req.body.data;
await db.disputeMap.upsert({
chargeflow_dispute_id: id,
source,
source_dispute_id: source_id,
});
res.sendStatus(200);
});From a scheduled backfill. Webhooks can be missed: your endpoint was down, or the dispute predates your registration. Run a job hourly or daily that pages through GET /public/2025-04-01/disputes and upserts any id you have not stored. The paging loop is in Backfill the last 90 days; reuse it with the same upsert.
You should see one row per Chargeflow dispute, and the backfill job should add zero rows on a healthy day.
Read before you write
Fetch the dispute with expand to see what Chargeflow already holds. Without expand, transaction and order come back as IDs; with it, they come back as full objects.
curl -G https://api.chargeflow.io/public/2025-04-01/disputes/DISPUTE_ID \
-H "x-api-key: $CHARGEFLOW_API_KEY" \
--data-urlencode "expand=transaction" \
--data-urlencode "expand=order"{
"id": "66e6ea9ecd94925a9f8060d9",
"source": "stripe",
"source_id": "du_123456789",
"reason": "fraud",
"status": "needs_response",
"transaction": {
"id": "66e6ea9ecd94925a9f8060da",
"amount": 150,
"currency": "USD",
"billing_address": null
},
"order": {
"id": "66e6ea9ecd94925a9f8060db",
"customer": {
"name": "John Doe",
"email": "john.doe@example.com",
"phone_number": null,
"ip_address": null
},
"products": [],
"fulfillments": []
}
}In this example Chargeflow holds the amount, name, and email from the processor, but no billing address, phone, IP address, or products. Those are your gaps. The full list of expansions (transaction, order, subscription, customer_communication) is on Get dispute by ID.
A 404 "Dispute not found" here almost always means a processor ID was passed as DISPUTE_ID. Resolve it through your mapping table from step 1.
Enrich only the gaps
Send the missing fields, and nothing else, to PATCH /public/2025-04-01/disputes/{disputeId}:
curl -X PATCH https://api.chargeflow.io/public/2025-04-01/disputes/DISPUTE_ID \
-H "x-api-key: $CHARGEFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"order": {
"customer": {
"phone_number": "1(123)4567890",
"ip_address": "203.0.113.42"
}
},
"transaction": {
"billing_address": {
"name": "John Doe",
"line1": "123 Main St",
"city": "New York",
"state": "NY",
"zip_code": "10001",
"country": "US"
}
}
}'You should get 200 with the updated dispute, now carrying the fields you sent.
Arrays are replaced, not merged
If you send products or fulfillments, send the complete array. Items you leave out are
removed. Details in Enrich a dispute.
Files and free-form data (usage reports, screenshots, CRM notes) do not go here. See where does my data go.
Track enrichment coverage
Record the outcome of each dispute in your own system, for example an enriched_at timestamp and the list of fields sent, next to the ID mapping. Then a daily query finds the disputes you did not enrich (no enriched_at, or a failed PATCH) so you can retry them while they are still inside the response window (due_by).
Next step
Testing first? See Test your integration.