> This page is for version v1 (default).
> For other versions, use one of these documentation indexes:
> - v1 (default): https://developers.truemed.com/v-1/llms.txt

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

# Webhooks and polling

> How Truemed notifies you of dispute activity in real time, and the recommended notify-then-fetch pattern.

Truemed notifies your systems about dispute activity in real time through three webhook events, delivered over the same
signed, retried webhook infrastructure as the rest of the Truemed API. The model is **notify, then fetch**: the webhook
tells you a dispute changed and carries a full snapshot, and you can re-fetch the dispute for authoritative state.

## Events

| Event             | Fires when                                                                                                                                                              |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dispute.created` | A dispute first becomes visible to you — a new chargeback, or a live inquiry you can respond to (see the [`inquiry` state](/guides/setup/api/disputes/overview#state)). |
| `dispute.updated` | An open dispute's `state` or `phase` changes (for example, after you submit evidence and it moves to `under_review`).                                                   |
| `dispute.closed`  | A dispute reaches a terminal state — a chargeback outcome (`won`, `lost`, `accepted`, `expired`) or a surfaced inquiry the bank drops (`closed`).                       |

> **Info**
>
> **Inquiries notify too — with one exception.** A live inquiry fires `dispute.created` (and `dispute.updated`) just like a
> chargeback, so you can respond to pre-empt escalation. If the bank later drops a surfaced inquiry, it reaches the terminal
> `closed` state and fires `dispute.closed` — so `dispute.closed` marks any terminal outcome, whether a chargeback result
> (`won`/`lost`/`accepted`/`expired`) or a dropped inquiry (`closed`). The one exception: an inquiry that opens *and* closes
> between webhook deliveries — already terminal the first time Truemed sees it — fires **no** webhook at all, since nothing
> was ever actionable.

Events are deduplicated on transitions: a re-sync of an already-terminal dispute does not re-fire `dispute.closed`, and
an update that doesn't change `state` or `phase` is silent.

## Payload

Dispute events use the [standard signed webhook envelope](/guides/setup/api/resources/webhooks#signed-webhooks): the
`data` field is the **full canonical dispute** — the same shape returned by
[`GET /api/v1/disputes/{dispute_id}`](/guides/setup/api/disputes/list-and-retrieve), including `dispute_id`, `state`, `phase`,
`reason`, `amount`, `due_by`, `counters`, and `metadata`. Because the snapshot is complete, you can often act on the
webhook directly; re-fetching is the safe fallback when a delivery is delayed or retried out of order.

```json
{
  "webhook_delivery_id": "dlv_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
  "event_type": "dispute.created",
  "data": {
    "dispute_id": "d1f8c2a0-3b4c-4d5e-8f90-1a2b3c4d5e6f",
    "processor_dispute_id": "du_1AbC2DeFgHiJkLmN",
    "processor": "STRIPE",
    "state": "needs_response",
    "phase": "dispute",
    "reason": "fraudulent",
    "network_reason_code": "10.4",
    "amount": 2000,
    "currency": "USD",
    "due_by": "2026-02-01T23:59:59Z",
    "created_at": "2026-01-15T12:00:00Z",
    "counters": [],
    "metadata": {},
    "evidence_requirements": {
      "merchant_explanation": "Show the cardholder authorized the purchase and received the goods: signed proof, a matching IP or device, and delivery confirmation.",
      "compelling": [
        { "category": "shipping_tracking_number", "label": "Shipping tracking number", "description": "Carrier tracking number showing delivery to the cardholder's address.", "text": true, "file": false },
        { "category": "customer_signature", "label": "Customer signature", "description": "A signed order form or delivery receipt.", "text": false, "file": true }
      ],
      "supporting": [
        { "category": "receipt", "label": "Receipt", "description": "The order receipt or invoice.", "text": false, "file": true }
      ],
      "other": [
        { "category": "refund_policy", "label": "Refund policy", "description": "Your published refund policy.", "text": false, "file": true }
      ]
    },
    "payment": {
      "order_id": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
    }
  }
}
```

Null fields are omitted, so `outcome` and `resolved_at` are absent until the dispute resolves (and `prior_dispute_id` is
absent unless this is a re-filing). `processor` and `currency` are upper-case (`"STRIPE"`, `"USD"`).

The delivery is signed with HMAC-SHA256; verify the `x-truemed-signature` header before processing. The header carries a
timestamp and the signature — for example `x-truemed-signature: t=1706108400,v0=5257a869…` — and the signed message is
`{t}.{raw_body}`. See [Signature verification](/guides/setup/api/resources/webhooks#signature-verification) for ready-to-use
snippets. Payloads carry canonical Truemed fields and the public `dispute_id` UUID — never raw processor payloads. Always
key your records on `dispute_id`.

## Subscribing

Dispute events use your existing sales-channel webhook configuration and respect its scope (testing vs. non-testing).
Add the `dispute.*` events to your webhook endpoint the same way you manage other Truemed webhook subscriptions, and
verify the HMAC signature on delivery as you do for all Truemed webhooks.

## Recommended pattern

1. Subscribe to `dispute.created`, `dispute.updated`, and `dispute.closed`.
2. On `dispute.created`, store the `dispute_id` and surface the open dispute to your team, noting `due_by`.
3. Respond before `due_by` — [submit evidence](/guides/setup/api/disputes/respond-with-evidence) or
   [accept](/guides/setup/api/disputes/accept-a-dispute).
4. On `dispute.updated` / `dispute.closed`, re-fetch the dispute (or trust the payload snapshot) to track progress and the
   final `outcome`.