# Deflect chargebacks (https://docs.chargeflow.io/docs/merchants/alerts/prevent-with-alerts)



Pre-chargeback alerts give you a chance to refund a transaction before it becomes a chargeback. (This recipe is about the [Alerts](https://docs.chargeflow.io/docs/merchants/alerts) product - stopping risky orders **before shipment** is [Prevent](https://docs.chargeflow.io/docs/merchants/prevent).) This recipe is a full, copy-paste-runnable flow for the merchant-managed model: subscribe to alert webhooks, receive an alert, refund the matching transaction in your processor, then report the outcome to Chargeflow so the alert is closed with the issuer.

## Before you start [#before-you-start]

* Get your `API Access Key`. Every request sends it in the `x-api-key` header. See [Authentication](https://docs.chargeflow.io/docs/reference/api-fundamentals/authentication).
* All requests go to `https://api.chargeflow.io` under the `/public/2025-04-01/` path.

<Callout title="Note">
  This recipe covers the merchant-managed model, where you refund and report outcomes yourself. With
  Chargeflow Alerts (the automated model), Chargeflow matches and refunds automatically, and you do
  not need to call the outcome endpoint.
</Callout>

## Steps [#steps]

<Steps>
  <Step>
    ### Subscribe to alert events [#subscribe-to-alert-events]

    Register a webhook endpoint for the alert lifecycle events.

    <Tabs items="['curl', 'Node.js', 'Python']">
      <Tab value="curl">
        Each registration subscribes one endpoint to one event, so register once per event:

        ```bash title="Terminal"
        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 '{"event": "alerts.created", "url": "https://your-server.com/webhooks/alerts-created"}'

        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 '{"event": "alerts.updated", "url": "https://your-server.com/webhooks/alerts-updated"}'
        ```
      </Tab>

      <Tab value="Node.js">
        ```javascript title="Node.js"
        // One registration per event
        for (const [event, path] of [
          ['alerts.created', '/webhooks/alerts-created'],
          ['alerts.updated', '/webhooks/alerts-updated'],
        ]) {
          const res = await fetch('https://api.chargeflow.io/public/2025-04-01/webhooks', {
            method: 'POST',
            headers: {
              'x-api-key': process.env.CHARGEFLOW_API_KEY,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({ event, url: `https://your-server.com${path}` }),
          });
          console.log(await res.json());
        }
        ```
      </Tab>

      <Tab value="Python">
        ```python title="Python"
        import os, requests

        # One registration per event
        for event, path in [
            ("alerts.created", "/webhooks/alerts-created"),
            ("alerts.updated", "/webhooks/alerts-updated"),
        ]:
            res = requests.post(
                "https://api.chargeflow.io/public/2025-04-01/webhooks",
                headers={"x-api-key": os.environ["CHARGEFLOW_API_KEY"]},
                json={"event": event, "url": f"https://your-server.com{path}"},
            )
            print(res.json())
        ```
      </Tab>
    </Tabs>

    See [Subscribe to Webhook Events](https://docs.chargeflow.io/docs/merchants/automation/subscribe-to-events) for the full list of events, and [Webhooks](https://docs.chargeflow.io/docs/reference/concepts/webhooks#verifying-a-webhook-signature) for signature verification.
  </Step>

  <Step>
    ### Receive the alerts.created webhook [#receive-the-alertscreated-webhook]

    When a card network raises a pre-chargeback alert, Chargeflow posts an `alerts.created` event to your endpoint. The payload carries the alert ID and the metadata you need to locate the transaction.

    ```json title="Webhook payload"
    {
      "id": "alert_123456789",
      "account_id": "account_987654321",
      "ext_account_id": "platform_acc_12345",
      "transaction": "txn_abcdef123456",
      "created_at": "2025-07-31T12:34:56Z",
      "status": "alerted",
      "status_date": "2025-07-31T13:00:00Z",
      "statement_descriptor": "My Merchant Store",
      "amount": 12500,
      "currency": "USD",
      "outcome": "pending",
      "type": "fraud_warning",
      "reason": "fraud",
      "network_transaction": {
        "id": "net_txn_78910",
        "created_at": "2025-07-31T12:30:00Z",
        "amount": 12500,
        "currency": "USD",
        "card_brand": "Visa",
        "bin": "411111",
        "last4": "1111",
        "auth_code": "AUTH1234",
        "arn": "ARN56789"
      }
    }
    ```

    Acknowledge with a `2XX` quickly, then handle the alert asynchronously.

    ```javascript title="Node.js"
    app.post('/webhooks/alerts', express.json(), (req, res) => {
      const alert = req.body;
      res.json({ received: true }); // acknowledge first

      // The alerts.created payload is delivered flat (no envelope): the alert
      // fields are at the top level. Subscribe this endpoint to alerts.created,
      // so every delivery is a new alert. alert.type holds the alert type,
      // for example 'fraud_warning'.
      handleAlert(alert.id);
    });
    ```
  </Step>

  <Step>
    ### Fetch the full alert (optional) [#fetch-the-full-alert-optional]

    Some fields, such as the matched `transaction` reference, may be filled in after ingestion. For the current, complete alert record, read it by ID.

    <Tabs items="['curl', 'Node.js', 'Python']">
      <Tab value="curl">
        ```bash title="Terminal"
        curl -X GET https://api.chargeflow.io/public/2025-04-01/alerts/alert_123456789 \
          -H "x-api-key: YOUR_API_KEY"
        ```
      </Tab>

      <Tab value="Node.js">
        ```javascript title="Node.js"
        async function handleAlert(alertId) {
          const res = await fetch(`https://api.chargeflow.io/public/2025-04-01/alerts/${alertId}`, {
            headers: { 'x-api-key': process.env.CHARGEFLOW_API_KEY },
          });
          const alert = await res.json();
          // Use alert.network_transaction.last4, .arn, .amount to find the charge
          return alert;
        }
        ```
      </Tab>

      <Tab value="Python">
        ```python title="Python"
        import os, requests

        res = requests.get(
            "https://api.chargeflow.io/public/2025-04-01/alerts/alert_123456789",
            headers={"x-api-key": os.environ["CHARGEFLOW_API_KEY"]},
        )
        alert = res.json()
        # Use alert["network_transaction"]["last4"], ["arn"], ["amount"] to find the charge
        ```
      </Tab>
    </Tabs>

    You can also list alerts with `GET /public/2025-04-01/alerts`, which supports `offset`, `limit`, `created_at_min`, `created_at_max`, `type`, `status`, `reason`, and `sort` query parameters.
  </Step>

  <Step>
    ### Refund the matching transaction [#refund-the-matching-transaction]

    Use the alert metadata to find the charge in your payment processor and refund it. Match on `network_transaction.last4`, `network_transaction.arn`, `amount`, and `currency`. This step happens in your own processor, not in the Chargeflow API.

    ```javascript title="Node.js"
    // Pseudocode against your processor's SDK
    const charge = await psp.findCharge({
      last4: alert.network_transaction.last4,
      arn: alert.network_transaction.arn,
      amount: alert.amount,
      currency: alert.currency,
    });

    await psp.refund(charge.id);
    ```
  </Step>

  <Step>
    ### Report the outcome [#report-the-outcome]

    After processing the alert, report the outcome with `POST /public/2025-04-01/alerts/{alertId}/outcome`. This is what closes the alert with the issuer. The request body takes a single required `outcome` field.

    Valid `outcome` values are `refunded`, `previously_refunded`, `duplicate`, `decline`, and `error`.

    <Tabs items="['curl', 'Node.js', 'Python']">
      <Tab value="curl">
        ```bash title="Terminal"
        curl -X POST https://api.chargeflow.io/public/2025-04-01/alerts/alert_123456789/outcome \
          -H "x-api-key: YOUR_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"outcome": "refunded"}'
        ```
      </Tab>

      <Tab value="Node.js">
        ```javascript title="Node.js"
        async function reportOutcome(alertId, outcome) {
          const res = await fetch(`https://api.chargeflow.io/public/2025-04-01/alerts/${alertId}/outcome`, {
            method: 'POST',
            headers: {
              'x-api-key': process.env.CHARGEFLOW_API_KEY,
              'Content-Type': 'application/json',
            },
            body: JSON.stringify({ outcome }),
          });

          // Returns 202 Accepted on success
          return res.status;
        }

        await reportOutcome('alert_123456789', 'refunded');
        ```
      </Tab>

      <Tab value="Python">
        ```python title="Python"
        import os, requests

        res = requests.post(
            "https://api.chargeflow.io/public/2025-04-01/alerts/alert_123456789/outcome",
            headers={"x-api-key": os.environ["CHARGEFLOW_API_KEY"]},
            json={"outcome": "refunded"},
        )
        # Returns 202 Accepted on success
        status = res.status_code
        ```
      </Tab>
    </Tabs>

    A successful call returns `202 Accepted`.

    <Callout type="warn" title="Warning">
      In the merchant-managed model, always call the outcome endpoint after handling an alert, even if
      you could not find the transaction. Without this step, Chargeflow cannot close the alert with the
      issuer and the chargeback may still be filed.
    </Callout>
  </Step>

  <Step>
    ### React to `alerts.updated` [#react-to-alertsupdated]

    As the alert moves through its lifecycle, Chargeflow fires `alerts.updated`. Use it to keep your records in sync. For example, when `outcome` is `prevented`, record the successful prevention.

    ```json title="Webhook payload"
    {
      "id": "alert_123456789",
      "account_id": "account_987654321",
      "status": "prevented",
      "status_date": "2025-07-31T14:00:00Z",
      "amount": 12500,
      "currency": "USD",
      "outcome": "prevented",
      "type": "fraud_warning",
      "reason": "fraud"
    }
    ```

    If `outcome` is `chargebacked`, the alert escalated to a chargeback despite mitigation. Watch for `dispute.created` if you want to begin enrichment. See [Automate a chargeback dispute](https://docs.chargeflow.io/docs/merchants/automation/automate-a-chargeback-dispute).
  </Step>
</Steps>

## Next steps [#next-steps]

<Cards>
  <Card title="alerts.created" href="/docs/merchants/webhook-events#alerts-created">
    The full alert payload reference.
  </Card>

  <Card title="alerts.updated" href="/docs/merchants/webhook-events#alerts-updated">
    Every outcome value and what it means.
  </Card>

  <Card title="Subscribe to Webhook Events" href="/docs/merchants/automation/subscribe-to-events">
    Register endpoints and verify signatures.
  </Card>

  <Card title="Automate a chargeback dispute" href="/docs/merchants/automation/automate-a-chargeback-dispute">
    What to do when an alert escalates to a chargeback.
  </Card>
</Cards>
