---
id: "api-reference/track-refund"
type: "doc"
url: "https://affitor.com/docs/api-reference/track-refund"
---
# Track Refund

> Reverse a commission when a sale is refunded

`POST /api/v1/track/refund`

Reverses the affiliate commission tied to a previously tracked sale. Send one call per refund. Each call's `refund_amount_cents` is added to what was already refunded on the sale (capped at the sale amount), and the commission is reduced proportionally: `refunded` while part of the sale remains, `reversed` once all of it is refunded. Omitting `refund_amount_cents` refunds everything that is left. This is the non-Stripe counterpart to the Stripe refund webhook — both use the same underlying reversal logic.

---

## Authentication

All requests must include your program API key as a Bearer token.

```http
Authorization: Bearer YOUR_PROGRAM_API_KEY
Content-Type: application/json
```

The key is scoped to a single program. The refund lookup is restricted to sales that belong to the authenticated program.

---

## Request

### Fields

<ParamList>
  <ParamField name="transaction_id" type="string" required>
    The `transaction_id` originally passed to `POST /api/v1/track/sale`. Used to locate the sale record.
  </ParamField>
  <ParamField name="refund_amount_cents" type="integer">
    This refund's amount in cents — not the running total refunded so far. Omit or set to `0` to refund the rest of the sale. Must be a positive integer when provided.
  </ParamField>
  <ParamField name="refund_id" type="string">
    Your own id for this refund (for example the Stripe refund id). Recommended. A refund whose `refund_id` was already applied is ignored, so retries are safe. Without it, a second call with the same amount for the same sale is treated as a retry and ignored. Ignored when the amount is omitted or `0` (refunding the rest is already safe to retry).
  </ParamField>
  <ParamField name="refund_reason" type="string">
    Human-readable reason for the refund. Recorded on the commission and included in partner/advertiser notifications.
  </ParamField>
</ParamList>

### Example request body

<CodeGroup items={[
  {
    label: "cURL",
    lang: "bash",
    code: `curl -X POST https://api.affitor.com/api/v1/track/refund \\
  -H "Authorization: Bearer YOUR_PROGRAM_API_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{
    "transaction_id": "txn_abc123",
    "refund_amount_cents": 5000,
    "refund_id": "re_123",
    "refund_reason": "Customer requested cancellation"
  }'`
  },
  {
    label: "Full refund",
    lang: "json",
    code: `{
  "transaction_id": "txn_abc123"
}`
  },
  {
    label: "Partial refund",
    lang: "json",
    code: `{
  "transaction_id": "txn_abc123",
  "refund_amount_cents": 5000,
  "refund_id": "re_123",
  "refund_reason": "Customer requested cancellation"
}`
  },
]} />

### Multiple refunds and retries

- Two partial refunds of $30 and $20 on a $100 sale: send `3000` then `2000` (each with its own `refund_id`). The commission is reduced by 50% in total.
- Retries: resending a call with the same `refund_id` returns `status: "skipped"` and changes nothing. Without `refund_id`, the amount is used to detect the retry.

---

## Response

### Success — commission reversed or refunded

```json
{
  "success": true,
  "status": "reversed",
  "deductionAmount": 25.00,
  "commission_id": 88
}
```

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Always `true` on a 200 response. |
| `status` | string | `"reversed"` (sale fully refunded), `"refunded"` (partially refunded), or `"skipped"` (refund already applied, or commission already reversed). |
| `deductionAmount` | number | Dollar amount deducted from the partner's commission balance. Present when `status` is `reversed` or `refunded`. |
| `commission_id` | integer | ID of the affected commission record. |

### Success — sale has no commission

```json
{
  "success": true,
  "status": "no_commission",
  "message": "Sale has no commission to reverse"
}
```

### Already-processed refund (idempotent)

```json
{
  "success": true,
  "status": "skipped",
  "reason": "already reversed",
  "commission_id": 88
}
```

---

## Errors

| Status | When |
|--------|------|
| 400 | `transaction_id` is missing from the request body. |
| 401 | `Authorization` header is missing, malformed, or the API token is invalid. |
| 404 | No sale record found for the given `transaction_id` in the authenticated program. |
| 500 | Internal error during commission reversal. |

---

## Side Effects

When a commission is reversed or refunded, Affitor:

- transitions the commission to `reversed` or `refunded` status
- deducts the proportional commission amount from the partner's and partner-program's balance (clamped at zero unless the commission was already paid, in which case the balance can go negative)
- sends an in-app notification to the partner
- sends a manual-review warning to the advertiser if the commission had already been paid out

These side effects are fire-and-forget and do not affect the HTTP response.

---

## Related

- [Track Sale](/docs/api-reference/track-sale) — record the original sale
- [Payment Tracking](/docs/brand/tracking/payment-tracking-stripe/) — Stripe webhook alternative
