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

# Server API

## Reporting orders: the server API

Base URL: your `s2s_endpoint_base` (e.g. `https://realry.com/api/v1/direct`). Every request is a `POST` with a JSON body and the three signature headers below.

### Signing a request

Every call carries three headers:

| Header                  | Value                                                 |
| ----------------------- | ----------------------------------------------------- |
| `X-Merchant-Key`        | your `api_key`                                        |
| `X-Signature-Timestamp` | the current unix time **in seconds** (a whole number) |
| `X-Signature`           | `HMAC_SHA256(timestamp + body, hmac_secret)`, as hex  |

To build the signature: take the timestamp, glue the **exact JSON body** directly onto the end of it (no space, no separator), and HMAC-SHA256 that string with your `hmac_secret`.

<Warning>
  **Build your JSON body once, sign that exact text, and send that exact text.** If you re-encode the body after signing (even just reordering keys), the signature won't match.
</Warning>

What the server checks — any failure returns `401`:

* All three headers are present.
* The timestamp is a whole number and within **5 minutes** of our clock. (Keep your server clock synced with NTP.)
* Your `api_key` is active.
* The signature matches.

Your key is tied to your account, so you never name a seller in the body — we already know it's you.

### The four events

You report an order's life with these calls. All take `merchant_order_id` (your order id) and a unique `event_id` (see *Safe retries* below).

#### 1. Order paid — `POST /conversions`

Send this the moment payment completes (card capture, or the bank-deposit confirmation for virtual-account orders — see *Virtual-account orders* below).

```json theme={null}
{
  "merchant_order_id": "order-1",
  "event_id": "evt-1",
  "amount": 49900,
  "currency": "KRW",
  "cb_aev": "ABC123",
  "product_name": "Silk Blouse",
  "items": [{ "item_id": "sku-7", "name": "Silk Blouse", "price": 49900, "quantity": 1 }]
}
```

| Field               | Required | Notes                                                                               |
| ------------------- | -------- | ----------------------------------------------------------------------------------- |
| `merchant_order_id` | yes      | your order id (same as the pixel `transaction_id`)                                  |
| `event_id`          | yes      | unique per call; used to make retries safe                                          |
| `amount`            | yes      | order total; a number, 0 or greater                                                 |
| `currency`          | yes      | 3-letter code, e.g. `KRW`                                                           |
| `cb_aev`            | no       | the click id you saved; omit it and the order isn't credited to a creator           |
| `product_name`      | no       | shown in the creator's dashboard; falls back to `items[0].name`, else left blank    |
| `items`             | no       | line items, for your own records                                                    |
| `sub_id`            | no       | your own click/tracking reference — we echo it back everywhere so you can reconcile |

Response:

```json theme={null}
{
  "data": {
    "merchant_order_id": "order-1",
    "transaction_id": "stylmatch:<your_seller_id>:order-1",
    "status": "CONFIRMED",
    "net_amount": 49900,
    "refunded_amount": 0,
    "currency": "KRW"
  }
}
```

`net_amount` is the order total minus any refunds. `transaction_id` is our own internal reference — you don't need to parse it; always match orders by your `merchant_order_id`.

#### 2. Partial refund — `POST /conversions/partial`

Reduces the order by `refund_amount` (a partial cancel or return).

```json theme={null}
{ "merchant_order_id": "order-1", "event_id": "evt-2", "refund_amount": 10000 }
```

Status becomes `PARTIALLY_CANCELLED` and `net_amount` drops. `refund_amount` must be between 0 and the current `net_amount`.

#### 3. Full cancel — `POST /conversions/cancel`

```json theme={null}
{ "merchant_order_id": "order-1", "event_id": "evt-3" }
```

Sets `net_amount` to 0, status `CANCELLED`, and reverses any commission.

#### 4. Don't credit this order — `POST /conversions/decline`

Use this when **you** determine one of your *own* paid channels actually won the sale, not the creator. A declined order is dropped from settlement and never invoiced.

```json theme={null}
{ "merchant_order_id": "order-1", "event_id": "evt-4", "reason": "own_google_ads_last_click" }
```

Status becomes `DECLINED`, `net_amount` 0. `reason` is a free-text note for your own records.

### Safe retries — `event_id`

<Tip>
  Give every call a unique `event_id`. If a request times out and you retry it, **reuse the same `event_id`** — we recognize it and won't double-count. Retrying is always safe.
</Tip>

### Virtual-account orders

For virtual-account (bank-transfer) payments, money isn't real until the customer deposits. So:

* Don't send the order report when the order is *created*.
* Send it when your **PG's deposit-confirmed webhook** fires (PortOne / Toss / NICE). That's the moment to call `POST /conversions`.
* If the deadline passes with no deposit, simply never send it. No cancel call is needed — an order we never heard about never settles.
