Automate a chargeback dispute
Receive a dispute.created webhook for any processor, fetch the dispute, attach evidence and notes, and let Chargeflow build and submit the representment for you.
This recipe is a full, copy-paste-runnable flow for responding to a chargeback with Chargeflow, whatever processor it came from. You receive the dispute.created webhook, fetch the dispute, attach supporting evidence and notes, and Chargeflow builds and submits the representment on your behalf, ahead of the deadline.
The flow is identical for every processor. Each dispute carries a source field (stripe, paypal, shopify_payments, braintree, and so on) telling you where it originated, but you call the same Chargeflow endpoints regardless. See Integrations for how each processor connection ingests disputes.
Chargeflow submits, you don't
You never call a submit endpoint or talk to the processor's dispute API. Once a dispute is in needs_response and your evidence is attached, Chargeflow assembles the evidence package and files the representment for you. This recipe is about feeding Chargeflow the context, not about submitting.
Before you start
- Connect at least one payment processor to Chargeflow. After that, new disputes from that processor are ingested automatically. See Integrations.
- Get your
API Access Key. Every request sends it in thex-api-keyheader. See Authentication. - Register a webhook endpoint for
dispute.created. See Subscribe to Webhook Events.
All requests go to https://api.chargeflow.io under the /public/2025-04-01/ path.
Steps
Register for the dispute.created event
Register a webhook endpoint so Chargeflow notifies you when a new dispute is ingested, from any connected processor.
curl -X POST https://api.chargeflow.io/public/2025-04-01/webhooks \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"event": "dispute.created"
}'Receive the dispute.created webhook
When a cardholder disputes a payment, Chargeflow ingests the dispute, runs its first enrichment pass, and posts a dispute.created event to your endpoint.
Note
The dispute.created event arrives wrapped in the delivery envelope: the dispute fields are inside data, and data.id is the Chargeflow dispute ID you use in the rest of this recipe.
Example payload (the source here is stripe, but it could be any connected processor):
{
"type": "dispute.created",
"webhookId": "123e4567-e89b-12d3-a456-426614174000",
"creationDate": "2025-01-27T10:00:00Z",
"data": {
"id": "dis_12345",
"source_id": "du_123456789",
"account_id": "act_112233",
"created_at": "2025-01-27T09:00:00Z",
"reason": "fraud",
"due_by": "2025-02-10T10:00:00Z",
"source": "stripe",
"amount": 150,
"currency": "USD",
"status": "needs_response",
"stage": "Chargeback",
"closed_at": null,
"transaction": "tx_1234567890",
"subscription": null,
"order": "ord_1234567890",
"customerCommunication": null
}
}Your endpoint must return a 2XX status promptly, then process the event asynchronously. Acknowledge first, work after.
const express = require('express');
const app = express();
app.post('/webhook', express.json(), (req, res) => {
const event = req.body;
// Acknowledge receipt immediately so Chargeflow does not retry
res.json({ received: true });
if (event.type === 'dispute.created') {
// Same flow for every processor - branch on event.data.source only if you
// need processor-specific handling on your side.
handleDispute(event.data.id);
}
});
app.listen(8000, () => console.log('Webhook handler running on port 8000'));Tip
Verify the X-Chargeflow-Hmac-Sha256 signature on every webhook in production. See Webhooks for the verification code.
Fetch the dispute
Use the Chargeflow dispute ID from data.id to read the full, current dispute record. Check the status, the due_by deadline, and which linked objects (transaction, order, subscription) Chargeflow has already matched.
curl -X GET https://api.chargeflow.io/public/2025-04-01/disputes/dis_12345 \
-H "x-api-key: YOUR_API_KEY"Example response:
{
"id": "dis_12345",
"source": "stripe",
"source_id": "du_123456789",
"account_id": "act_112233",
"created_at": "2025-01-27T09:00:00Z",
"reason": "fraud",
"due_by": "2025-02-10T10:00:00Z",
"amount": 150,
"currency": "USD",
"status": "needs_response",
"stage": "Chargeback",
"closed_at": null,
"transaction": "tx_1234567890",
"subscription": null,
"order": "ord_1234567890"
}Respond only while status is needs_response. Once the response is filed with the issuer, the status moves to under_review.
Add evidence
Attach the files that refute the claim. This is a multipart/form-data request. The dispute ID is the path parameter, and each call uploads one file with an evidenceUploadCategory.
For a fraud chargeback, proof of delivery and customer communication are the strongest artifacts.
curl -X POST https://api.chargeflow.io/public/2025-04-01/disputes/dis_12345/evidence \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@/path/to/delivery-confirmation.pdf" \
-F "evidenceUploadCategory=tracking_information"A successful upload returns the evidence ID:
{
"requestId": "req_abc123def456",
"evidenceId": "evd_789xyz"
}Supported categories are tracking_information, customer_communication, invoice, additional_evidence, and additional_notes. Files can be PNG, JPG, or PDF, up to 5 MB. See Upload Evidence for the full reference.
Add notes
Notes are uploaded through the same endpoint using the additional_notes category. This category requires a content field with your free-form text, alongside the file.
curl -X POST https://api.chargeflow.io/public/2025-04-01/disputes/dis_12345/evidence \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@/path/to/account-history.pdf" \
-F "evidenceUploadCategory=additional_notes" \
-F "content=Customer has 4 prior undisputed orders to the same address. IP and billing ZIP match the order on file."Tip
Add evidence as early as possible after dispute.created. Chargeflow folds your uploads into the evidence package before submission, so the sooner they arrive, the more the AI can build around them.
Let Chargeflow submit the response
You do not call a separate submit endpoint, and you never touch the processor's own dispute API. Once a dispute is in needs_response and your evidence is attached, Chargeflow assembles the evidence package and submits the representment to the issuer ahead of the due_by deadline.
Track progress by reading the dispute again. The status moves from needs_response to under_review after submission, and to won or lost once the issuer decides.
curl -X GET https://api.chargeflow.io/public/2025-04-01/disputes/dis_12345 \
-H "x-api-key: YOUR_API_KEY"