# Adjustments

How a merchant’s refund, cancellation, return or price change becomes a reversal on the buyer’s side, and how the reversal is rendered back in the merchant’s own protocol.

Status: Live · Updated: 2026-09-13
For: seller
Source: https://enruta.ai/docs/adjustments

UCP, ACP, Adyen and Shopify each have an `adjustments[]` notification, and none of them executes anything or says what the refund does to the buyer’s limit. Enruta reads your adjustment, decides and executes the [reversal](https://enruta.ai/docs/reversal) on the buyer’s side, and gives you the same reversal rendered in all four shapes.

## Report an adjustment

```http
POST /v0/sellers/adjustments           Authorization: Bearer enruta_slr_…
{
  "domain": "reamandrule.com",
  "order_id": "ord_01M2512SM3V3A9R4EBJM8RRRV5",
  "protocol": "ucp",                                   // ucp | acp | shopify | adyen
  "adjustment": { "id": "adj_01M2G8Y2K5T8W3Q6N9B1D4H7XR", "type": "refund", "amount": { "value": "9.99", "currency": "USD" },
                  "reason": "customer_request", "occurred_at": "2026-09-13T15:03:40Z", "status": "completed" },
  "signature": "eyJhbGciOiJFUzI1NiIs…"                  // optional: your detached JWS over the adjustment
}

201 {
  "reversal": { "id": "rev_01M2G8YV5R3T7W2Q9K4N8B6DXP", "kind": "refund", "stage": "settled", "status": "completed", "mandate_effect": "none", "reversal_record_id": "rec_01M2G8Z0Q4W7T2K9N5B3D6H1XC" },
  "adjustments": {
    "ucp":     { "type": "refund", "amount": 999, "currency": "USD", "reason": "customer_request", "reference": "rev_01M2G8YV5R3T7W2Q9K4N8B6DXP" },
    "acp":     { "type": "refund", "amount": 999, "currency": "USD", "reason": "customer_request", "reference": "rev_01M2G8YV5R3T7W2Q9K4N8B6DXP" },
    "adyen":   { "pspReference": "rev_01M2G8YV5R3T7W2Q9K4N8B6DXP", "type": "refund", "status": "completed" },
    "shopify": { "kind": "refund", "amount": "9.99", "currency": "USD", "reason": "customer_request" }
  }
}
```

The reversal is created with `initiator: merchant`, `kind` mapped from your protocol’s type, `protocol_ref` and `dedupe_key = "<order_id>:<adjustment id>"`, so reporting the same adjustment twice creates one reversal. `POST /v0/sellers/reversals` takes the same body with execute semantics: Enruta drives the buyer’s rail when it can (the sandbox rail at once, Stripe Issuing on test keys); a refund you already made in your own PSP stays `pending` until the buyer’s rail posts it or you report `status: completed`.

## Or let the order webhook do it

If your store already sends the UCP order webhook to Enruta, an `order_updated` event whose order carries new entries in `adjustments[]` reports each of them, deduplicated by `order_id:adjustment.id`; an `order_shipped` event (or a `shipped` event in `fulfillment.events[]`) confirms fulfillment. The reference merchant’s admin “Refund” and “Ship” actions work exactly this way.

## How types map

| Protocol | Its `adjustments[].type` values | Mapping to `reversal.kind` |
|---|---|---|
| UCP | 6: `refund`, `return`, `credit`, `price_adjustment`, `dispute`, `cancellation` | One to one; `dispute` ↔ `chargeback_credit` |
| ACP | 5 | One to one; `chargeback_credit` renders as `dispute` |
| Adyen Order API | 5, with `status` pending / completed / failed | `status` maps to the reversal’s `pending` / `completed` / `failed` |
| Shopify | 8, of which 5 are reversals: `refund`, `cancellation`, `return`, `exchange`, `order_edit` | Those five map in; the three transaction-record types are not reversals |

## What happens on the buyer’s side

- The reversal is decided against the buyer’s policy (amount within the original, stage, finality, window) and sealed as a linked record chained to the payment; when you signed the adjustment, the record’s subject is `third_party_signed` under your key.
- The buyer’s mandate account and budget are refilled according to the buyer’s `refill_policy`; you see the `mandate_effect` in the answer but never the buyer’s cap.
- The buyer’s agent is told through the `reversal.completed` webhook and the `reversals[]` on its payment request.
- Your reconciliation slice gains an `adjustments_leg` entry with `source: merchant_webhook`; when the buyer’s rail posts the refund, a second entry with `source: rail` matches it.

## Interfaces

| Surface | Call |
|---|---|
| REST, sellers | `POST /v0/sellers/adjustments`, `POST /v0/sellers/reversals`, `POST /v0/sellers/fulfillments`, `POST /v0/ucp/orders` (`order_updated`, `order_shipped`) |
| REST, buyer side | `GET /v0/reversals/{id}/adjustments?protocol=ucp\|acp\|adyen\|shopify` |
| SDK | `sellers.adjustments.report(body)`, `sellers.reversals.request(body)`, `sellers.fulfillments.confirm(body)` |
| Console (seller) | `/orders/{id}`: the adjustments table with the delivery state; `/reversals` |

Planned: adapters that read Adyen’s Order API and Shopify’s `orders/updated` for you (Adyen and Checkout.com January 2027; Shopify October 2026), so a refund made in your PSP’s dashboard becomes a reversal without a call from your code.
