> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mnai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tracking a payment link payment with webhooks

> Follow a payment link payment from creation to completion, failure, or reversal using payment link transaction webhooks.

Meridian sends a webhook every time a payment link transaction changes status. Each webhook carries both the payment link and the transaction, so you can tie a payment to its link without another API call. This works for single and recurring links.

## Before you start

Ask Meridian to register an endpoint for each event you want. Meridian registers endpoints per event name, so register all four to follow a payment end to end:

* `payment_link_transaction_created`
* `payment_link_transaction_status_updated`
* `payment_link_transaction_succeeded`
* `payment_link_transaction_failed`

Meridian signs these webhooks the same way as every other webhook. See [Security](/products/realtime-collections/webhooks-overview#security) to verify them.

## Route the event

Every delivery includes these headers:

| Header | Value |
| :- | :- |
| `X-Meridian-Resource-Type` | `payment_link_transaction` |
| `X-Meridian-Event-Type` | One of the four events above |

The body has no envelope. It is a JSON object with `paymentLink` and `transaction`, and the event name is only in the `X-Meridian-Event-Type` header. Read the header first, then act on `transaction.status`.

```json theme={null}
{
  "paymentLink": {
    "id": "usmer-n1hpx2d89pdpnmpdjlorb995",
    "status": "ACTIVE",
    "referenceId": "INV-7755-1791463748",
    "...": "..."
  },
  "transaction": {
    "id": "uspay-echo1fuz76usacpgicexz5vb",
    "createdAt": "2026-10-08T12:51:39.147Z",
    "updatedAt": "2026-10-08T12:51:49.571Z",
    "completedAt": "2026-10-08T12:51:49.570Z",
    "status": "COMPLETED",
    "previousStatus": "USER_DEDUCTED",
    "statusReason": null,
    "debitAmount": "150.00",
    "debitCurrency": "USD",
    "creditAmount": "150.00",
    "creditCurrency": "USD",
    "fee": "0.00"
  }
}
```

`paymentLink` is the same object the Payment Links API returns. See [Payment Link Transaction](/products/realtime-collections/api-reference/webhooks/webhooks-payment-link-transaction) for the full schema and an example of each event.

Amounts are decimal strings, timestamps are UTC, and fields with no value are sent as `null` instead of being omitted. The payload does not include payer names, bank accounts, or instrument details.

## Status flow

A successful payment moves through these statuses:

`PENDING` → `PROCESSING` → `USER_DEDUCTED` → `COMPLETED`

| Status change | Event |
| :- | :- |
| Transaction created as `PENDING` | `payment_link_transaction_created` |
| `PROCESSING`, `USER_DEDUCTED` | `payment_link_transaction_status_updated` |
| `COMPLETED` | `payment_link_transaction_succeeded` |

### Failures

A transaction that reaches `FAILED`, `DEBIT_FAILED`, `RETURNED`, or `REVERSED` sends `payment_link_transaction_failed`. The `statusReason` field holds the failure reason when there is one.

For example, a failed payment sends `status: FAILED`, `previousStatus: PROCESSING`, and `statusReason: INSUFFICIENT_FUNDS`.

### Reversals

A reversal sends `payment_link_transaction_status_updated` at `REVERSAL_IN_PROGRESS`. It sends `payment_link_transaction_failed` only when it reaches `REVERSED`.

### Other statuses

These statuses also send `payment_link_transaction_status_updated`: `PENDING_MANUAL_REVIEW`, `EDD_REQUIRED`, `REFUND_IN_PROGRESS`, `REFUNDED`, `PARTIALLY_REFUNDED`, `TIMED_OUT_REVERSAL_DECISION_PENDING`, `RETURN_IN_PROGRESS`, and `NOT_RECEIVED`.

## Handle the webhooks correctly

<Steps>
  <Step title="Treat transaction.status as the source of truth">
    Use `transaction.status` and `transaction.previousStatus` to decide what happened, not the event name alone.
  </Step>

  <Step title="Expect repeated failed events">
    Each failure status is its own change, so one transaction can send `payment_link_transaction_failed` more than once. For example, `FAILED` then `DEBIT_FAILED`, or `COMPLETED` then `RETURNED` after an ACH return. Handle repeated `failed` events for the same `transaction.id`.
  </Step>

  <Step title="Don't trust paymentLink.status for the outcome">
    `paymentLink` is a snapshot from when Meridian sent the webhook. Meridian updates the link and the transaction separately, so the link's `status` can lag the transaction. In `payment_link_transaction_created` the link can still be `ACTIVE`, and in `payment_link_transaction_failed` it can show its previous status. Fetch the link through the API if you need its final state.
  </Step>

  <Step title="Process idempotently">
    Key on `transaction.id` plus `transaction.status`. Each status change sends exactly one event, but retries can still deliver the same webhook more than once.
  </Step>

  <Step title="Order by updatedAt">
    Meridian does not guarantee delivery order. Use `transaction.updatedAt` to order events for the same transaction.
  </Step>

  <Step title="Reconcile through the API">
    Delivery is best effort. Meridian retries a failed delivery up to 3 times, a few hundred milliseconds apart. It is not a guaranteed, replayable queue, so periodically reconcile your records through the API.
  </Step>
</Steps>

## Behavior change

Payment link transactions no longer send the generic `transaction_created`, `transaction_succeeded`, and `transaction_failed` events. They send the `payment_link_transaction_*` events instead.

`REVERSAL_IN_PROGRESS` used to be reported as a failure and is now a `payment_link_transaction_status_updated` event. Statuses such as `PROCESSING` and `USER_DEDUCTED` were not reported before. The `transaction_*` events still exist and are unchanged for Meridian Accounts.
