Billing

Refunds

Return money to a customer for a captured payment, in full or in part. Refunds work the same way whatever payment rail took the original charge, and the amount is always re-derived on our side so you can never refund more than what is left.

Issue a refund

Refund a payment by its id. Omit the amount to return the full remaining balance, or pass an amount for a partial refund. Send an Idempotency-Key so a retried request returns the original refund instead of issuing a second one.

bash
curl -X POST https://api.meridapay.com/payments/<payment-id>/refund \
  -H "Authorization: Bearer $MERIDA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: refund-2026-09-09-001" \
  -d '{ "amount": "49.99" }'

With the TypeScript SDK:

ts
await merida.POST("/payments/{id}/refund", {
  params: { path: { id: paymentId } },
  body: { amount: "49.99" },
  headers: { "idempotency-key": "refund-2026-09-09-001" },
})

The full request and response, including the reason field and the error cases, live in the API reference. You never send a currency: we refund in the currency of the original charge and reject an amount larger than the captured total minus what was already refunded.

Refund status

A refund comes back with one of two states:

succeededstatus

The money is on its way back to the customer. Nothing more to do.

pendingstatus

The refund was accepted but the funds have not landed back yet, which happens with some payment methods that take a day or two to settle. It completes on its own. Listen for the webhook below, or re-fetch the payment to read its final state.

While a refund is pending, its amount is already held against the payment's refundable balance, so you cannot refund the same money twice.

Refunds under a partner group key

Partners refund a practice's payment with their group key, without the practice signing in. The identifier is always the payment id, and it covers every payment type. For a checkout (a one-off or a payment request) you read payment_id back from GET /partner/practices/{id}/checkouts/{checkoutId} once it is paid, or from the public GET /checkouts/{id}. For an off-session charge (an installment with no checkout) the same payment_id comes back on the charge response itself. Store it at payment time and pass it straight to the refund.

bash
curl -X POST \
  https://api.meridapay.com/partner/practices/<practice-id>/payments/<payment-id>/refund \
  -H "Authorization: Bearer $MERIDA_GROUP_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: refund-2026-09-14-001" \
  -d '{ "amount": "50.00" }'

Full and partial work the same as the org-key refund above, and the response carries the same id, payment_id, amount, currency, and status. A payment that belongs to another practice or group is a 404, never a cross-tenant refund.

To validate a partial before you send it, or to reconcile afterwards, read the payment's refund state:

bash
curl https://api.meridapay.com/partner/practices/<practice-id>/payments/<payment-id> \
  -H "Authorization: Bearer $MERIDA_GROUP_KEY"

It returns amount_captured, amount_refunded, refundable_amount, and a refund_status of none, pending, partially_refunded, or refunded.

To receive refund webhooks for every practice at one URL instead of registering one per practice, register a group-level endpoint. See Group endpoints under Webhooks.

Webhook events

payment.refundedwebhook

A refund completed and the funds are on their way back. Fires for full and partial refunds, and once a pending refund finishes settling. The payload carries the paymentId, the refund amount, and the round-trip fields from the original checkout.

payment.refund_failedwebhook

A refund that was accepted could not be completed. The payment stays in its prior settled state and the funds were not returned.

See Webhooks for delivery, signing, and retry behavior.

From the dashboard

Merchants can refund without writing code. Open an order under Revenue, click Refund, and enter an amount up to the refundable balance. The same status and events above apply, so a pending refund shows as processing until it settles.