> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runpayments.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runpayments.io/_mcp/server.

# Integration Examples

Working webhook handlers for Node.js and Python, and payloads for the chargeback, funding and statement events. The [Webhooks Overview](/docs/guides/webhooks/overview) explains the envelope, the signature, duplicates and retries.

Each handler does four things, in this order:

* Checks the signature against the raw body and responds `401` if it doesn't match.
* Skips the event if its `metadata.idempotency_key` has already been handled.
* Saves the event, then records the key. If saving fails, it responds `500` so the event is retried.
* Responds `200` straight away. Do any slow work later, from what you saved.

`isValidSignature` and `is_valid_signature` are the functions from [Verifying Signatures](/docs/guides/webhooks/overview#verifying-signatures). The `alreadyHandled`, `saveEvent` and `markHandled` functions stand in for your own storage.

## Node.js / Express

```javascript
const express = require("express");

const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// isValidSignature: see Verifying Signatures in the Webhooks Overview.

// Keep the raw body: the signature is computed over the exact bytes sent.
app.post("/webhooks/run", express.raw({ type: "*/*" }), async (req, res) => {
  if (!isValidSignature(req.body, req.get("X-Webhook-Signature-256"), WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }

  const webhook = JSON.parse(req.body.toString("utf8"));
  const key = webhook.metadata.idempotency_key;

  if (await alreadyHandled(key)) {
    return res.sendStatus(200);
  }

  try {
    await saveEvent(webhook);
    await markHandled(key);
  } catch (err) {
    console.error("Could not save webhook", err);
    return res.sendStatus(500);
  }

  res.sendStatus(200);
});

app.listen(3000);
```

## Python / Flask

```python
import json
import os

from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]

# is_valid_signature: see Verifying Signatures in the Webhooks Overview.


@app.post("/webhooks/run")
def handle_webhook():
    raw_body = request.get_data()  # raw bytes, before JSON parsing
    if not is_valid_signature(raw_body, request.headers.get("X-Webhook-Signature-256"), WEBHOOK_SECRET):
        return "", 401

    webhook = json.loads(raw_body)
    key = webhook["metadata"]["idempotency_key"]

    if already_handled(key):
        return "", 200

    try:
        save_event(webhook)
        mark_handled(key)
    except Exception:
        app.logger.exception("Could not save webhook")
        return "", 500

    return "", 200
```

## Reporting Event Payloads

These are the `payload` objects for the reporting events. They arrive in the standard envelope, and `source_id` is the Run merchant ID. Dates in these payloads are strings in `MM/DD/YYYY` format.

### `chargeback.entered`

Sent when a chargeback is recorded for a merchant.

```json
{
  "event_table_pk": 82043,
  "event_type": "chargeback.entered",
  "mid": "800000000000",
  "case_number": "700000000001",
  "card_number": "51XXXXXXXXXX0000",
  "card_brand_id": 2,
  "transaction_date": "10/01/2026",
  "transaction_amount": 44.00,
  "chargeback_date": "10/23/2026",
  "chargeback_amount": 44.00,
  "chargeback_reason": "No Cardholder Authorization",
  "_timestamp": "2026-10-23T04:16:57.310+00:00"
}
```

| **Field**            | **Data Type** | **Comments**                                                      |
| -------------------- | ------------- | ----------------------------------------------------------------- |
| `event_table_pk`     | Integer       | Run's ID for the chargeback record.                               |
| `event_type`         | String        | Always `chargeback.entered`.                                      |
| `mid`                | String        | Merchant ID assigned by the processor.                            |
| `case_number`        | String        | Chargeback case number from the processor.                        |
| `card_number`        | String        | Masked card number.                                               |
| `card_brand_id`      | Integer       | Card brand of the disputed transaction.                           |
| `transaction_date`   | String        | Date of the original transaction (`MM/DD/YYYY`).                  |
| `transaction_amount` | Number        | Amount of the original transaction.                               |
| `chargeback_date`    | String        | Date of the chargeback (`MM/DD/YYYY`).                            |
| `chargeback_amount`  | Number        | Amount charged back.                                              |
| `chargeback_reason`  | String        | Reason given for the chargeback.                                  |
| `_timestamp`         | String        | When the event was created, in ISO 8601 format with a UTC offset. |

### `funding.entered`

Sent when funding information is recorded for a merchant.

```json
{
  "event_table_pk": 5242032,
  "event_type": "funding.entered",
  "mid": "800000000000",
  "funding_master_id": "20000001",
  "dda_number": "XXXXX12345",
  "aba_number": "011000015",
  "funding_date": "11/02/2026",
  "dba_name": "Example Coffee Co",
  "net_sales_amount": 1250.00,
  "third_party_amount": 0,
  "adjustments_amount": 0,
  "interchange_fees_amount": -21.40,
  "service_charges_amount": -3.15,
  "fee_amount": -7.65,
  "reversals_amount": 0,
  "other_adjustments_amount": 0,
  "funding_amount": 1217.80,
  "_timestamp": "2026-11-02T04:16:57.309+00:00"
}
```

| **Field**                  | **Data Type** | **Comments**                                                      |
| -------------------------- | ------------- | ----------------------------------------------------------------- |
| `event_table_pk`           | Integer       | Run's ID for the funding record.                                  |
| `event_type`               | String        | Always `funding.entered`.                                         |
| `mid`                      | String        | Merchant ID assigned by the processor.                            |
| `funding_master_id`        | String        | Processor's ID for the funding batch.                             |
| `dda_number`               | String        | Number of the bank account funded, masked to its last 5 digits.   |
| `aba_number`               | String        | Routing number of the bank account funded. Sometimes masked.      |
| `funding_date`             | String        | Date of the deposit (`MM/DD/YYYY`).                               |
| `dba_name`                 | String        | Merchant's DBA name.                                              |
| `net_sales_amount`         | Number        | Net sales in this funding.                                        |
| `third_party_amount`       | Number        | Amount paid by third parties.                                     |
| `adjustments_amount`       | Number        | Adjustments.                                                      |
| `interchange_fees_amount`  | Number        | Interchange fees.                                                 |
| `service_charges_amount`   | Number        | Service charges.                                                  |
| `fee_amount`               | Number        | Fees.                                                             |
| `reversals_amount`         | Number        | Reversals.                                                        |
| `other_adjustments_amount` | Number        | Other adjustments.                                                |
| `funding_amount`           | Number        | Total amount deposited. Can be negative.                          |
| `_timestamp`               | String        | When the event was created, in ISO 8601 format with a UTC offset. |

### `statement.entered`

Sent when a merchant statement is available.

```json
{
  "event_table_pk": 31500,
  "event_type": "statement.entered",
  "mid": "800000000000",
  "statement_date": "04/01/2026",
  "statement_filename": "800000000000_0000_B000_APR-01-2026.pdf",
  "_timestamp": "2026-04-01T22:16:31.924+00:00"
}
```

| **Field**            | **Data Type** | **Comments**                                                      |
| -------------------- | ------------- | ----------------------------------------------------------------- |
| `event_table_pk`     | Integer       | Run's ID for the statement record.                                |
| `event_type`         | String        | Always `statement.entered`.                                       |
| `mid`                | String        | Merchant ID assigned by the processor.                            |
| `statement_date`     | String        | Statement date (`MM/DD/YYYY`).                                    |
| `statement_filename` | String        | File name of the statement.                                       |
| `_timestamp`         | String        | When the event was created, in ISO 8601 format with a UTC offset. |