# In-store payment: Local API

## Overview

The Local API lets your POS application drive a Yavin terminal over the local network: start a payment, print or share a receipt, reverse or abort a transaction. Choose it when the POS and the terminal are on different devices but share the same network.

## At a glance

|  |  |
| --- | --- |
| Base URL | http://<LOCAL_IP>:16125/localapi/v4 |
| Authentication | None on the API itself: the merchant must be logged in to Yavin Pay with their [My Yavin](https://my.yavin.com/) credentials (terminals can be pre-configured by our onboarding team) |
| Naming convention | camelCase for all fields, except the entries of tax_breakdown which are snake_case |
| Current version | v4 |
| Result delivery | Synchronous: the HTTP response contains the transaction result |

## Endpoints

| Endpoint | Method | Purpose |
| --- | --- | --- |
| /ping | GET | Check connectivity and configuration |
| /payment | POST | Start a debit or refund transaction |
| /print | POST | Print free content (receipt, invoice) |
| /share-receipt | POST | Share a receipt by SMS, email or print |
| /reversal | POST | Reverse a recent transaction (less than 16 hours old) |
| /abort | POST | Abort the ongoing payment |

### Versions

| API version | Last updated |
| --- | --- |
| v4 | 29/04/2026 |
| v1 | 02/05/2022 |

## Before you start

Network. The terminal must be connected to the same local network as your POS, and that network must have internet access to approve transactions. Address the terminal by its local IP, or preferably by Network Service Discovery (Apple Bonjour compatible), which survives IP changes: see [Add on: Protocole Bonjour](https://app.notion.com/p/3bc9a8f4fd9a8198a397cff4e0940c72).

Port. Always include port 16125 in the URL (easy to remember: P=16, A=1, Y=25).

Protocol. HTTP by default, which is enough for a secured network between the cash register and the terminal. For HTTPS and response signature, see [Add on: Security, signature and HTTPS](https://app.notion.com/p/3bc9a8f4fd9a810b8dd6e87039cab666). Both are conditional: HTTPS requires the terminal to have retrieved its certificate, and responses are signed only when signature is enabled for your company.

Responses. The endpoints documented on this page return a JSON body with a status field: ok on success, ko on failure. Application errors are returned with HTTP 200 and {"status": "ko", "message": "..."}; error messages are quoted verbatim under each endpoint. A malformed JSON body is rejected with an HTTP 400 or 500 without a JSON body: check the HTTP status before parsing the response. Payment endpoints return the Transaction object, documented at the end of this page along with the other shared objects. Keys whose value is null are omitted from responses: your parser must tolerate missing keys.

Timeouts. A payment has a 120 second window between the moment the request is received and a successful card read, split in two phases:

1. Up to 60 seconds for optional screens (tips, reference, review). If this phase times out, the transaction is aborted.

1. Up to 60 seconds for the card read itself.

> 💡 Client-side timeout to set: 125 seconds for classic terminal use, 62 seconds for kiosk use (optional screens disabled) and for refunds.

Idempotency. Send an idempotentUuid on every payment request, one per payment attempt. See [IdempotentUuid Management](https://app.notion.com/p/3bc9a8f4fd9a81d6a3cbfce4939ac0e9). The terminal keeps a payment request open for up to 15 minutes. If your client-side timeout expires, resend the same request with the same idempotentUuid: if the payment is still in progress, the new request attaches to it and returns its result.

Currency. The currency is the one configured on the merchant profile in the Yavin database. Supported today: EUR, CHF, GBP.

---

## Endpoints

### Ping terminal

`GET http://<LOCAL_IP>:16125/localapi/v4/ping`

Verifies your configuration. A good practice is to add a TEST button in your POS settings screen that calls this route and displays the result.

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `showMessage` | Boolean | no | false | true: displays "Hello there" on the terminal. false: nothing is shown |

#### Request

_cURL_
```bash
curl -X GET 'http://<LOCAL_IP>:16125/localapi/v4/ping'
```

_Node_
```javascript
(async () => {
  const response = await fetch("http://<LOCAL_IP>:16125/localapi/v4/ping", {
    method: "GET",
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import requests

response = requests.get(
    "http://<LOCAL_IP>:16125/localapi/v4/ping",
    timeout=125,
)
response.raise_for_status()
data = response.json()
```

#### Response

_JSON_
```json
{ "status": "ok" }
```

### Start a debit transaction

`POST http://<LOCAL_IP>:16125/localapi/v4/payment`

Starts a debit payment on the terminal. Specify the amount and optional parameters; the terminal handles the payment flow and returns the result synchronously.

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `amount` | Integer | yes |  | Amount in cents, must be greater than 0 |
| `transactionType` | String | no | debit | Use debit. Values are strict (debit or refund): any other value, credit included, is silently processed as a debit |
| `idempotentUuid` | String | no | null | Unique identifier of the payment attempt. If the terminal knows a transaction with this UUID (last 24h), it returns that transaction instead of starting a new one. A ko transaction is returned as is: generate a new UUID to retry. If you do not provide one, Yavin generates one internally, which does not protect your POS from double submission |
| `cartId` | String | no | null | Your order number, shown in the MyYavin backoffice to link a transaction to an order |
| `customer` | Customer | no |  | Customer information attached to the transaction |
| `enableGiftScreen` | Boolean | no | null | null (default): the terminal tips configuration applies. A non-null value overrides it for this transaction: true shows the tips screen, false skips it |
| `acceptedPayment` | AcceptedPayment | no | {"acceptedMediumType":"all"} | Restricts the payment methods the terminal accepts for this transaction. See The AcceptedPayment object at the end of this page |
| `giftAmount` | Integer | no | 0 | Tip or donation in cents, if the tip was selected on the POS side |
| `receiptTicket` | ReceiptTicket | no |  | Receipt content printed together with the card ticket |
| `reference` | String | no | null | Waiter or person processing the transaction |
| `vendor` | Vendor | no |  | Your software name and version, useful for support and debugging |
| `checkoutExternalId` | String | no | null | Merchant-unique checkout ID, required when any external order field below is sent. It must be unique: a request is rejected if a completed ok transaction already uses this value, so send a different value for each transaction of a split payment |
| `externalTableNumber` | String | no | null | Table number. Only used with checkoutExternalId |
| `externalOrderNumber` | String | no | null | Human-readable order number shown to merchant and customer. Only used with checkoutExternalId |
| `externalOrderId` | String | no | null | Unique technical order ID from your POS, for reconciliation. Only used with checkoutExternalId |

#### Request

_JSON_
```json
{
  "amount": 1000,
  "transactionType": "debit",
  "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51",
  "vendor": {
    "softwareName": "MyPOS",
    "softwareVersion": "1.0"
  },
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  },
  "customer": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@yavin.com"
  },
  "acceptedPayment": {
    "acceptedMediumType": "all"
  },
  "checkoutExternalId": "6843z9fgved8",
  "externalTableNumber": "54",
  "externalOrderNumber": "642558",
  "externalOrderId": "14763642558"
}
```

_cURL_
```bash
curl -X POST 'http://<LOCAL_IP>:16125/localapi/v4/payment' \
  -H 'Content-Type: application/json' \
  -d '{
  "amount": 1000,
  "transactionType": "debit",
  "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51",
  "vendor": {
    "softwareName": "MyPOS",
    "softwareVersion": "1.0"
  },
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  },
  "customer": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@yavin.com"
  },
  "acceptedPayment": {
    "acceptedMediumType": "all"
  },
  "checkoutExternalId": "6843z9fgved8",
  "externalTableNumber": "54",
  "externalOrderNumber": "642558",
  "externalOrderId": "14763642558"
}'
```

_Node_
```javascript
(async () => {
  const response = await fetch("http://<LOCAL_IP>:16125/localapi/v4/payment", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "amount": 1000,
      "transactionType": "debit",
      "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51",
      "vendor": {
        "softwareName": "MyPOS",
        "softwareVersion": "1.0"
      },
      "receiptTicket": {
        "data": "This is the receipt ticket\nto print",
        "format": "text"
      },
      "customer": {
        "firstName": "John",
        "lastName": "Doe",
        "email": "john@yavin.com"
      },
      "acceptedPayment": {
        "acceptedMediumType": "all"
      },
      "checkoutExternalId": "6843z9fgved8",
      "externalTableNumber": "54",
      "externalOrderNumber": "642558",
      "externalOrderId": "14763642558"
    }),
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import requests

headers = {
    "Content-Type": "application/json",
}

payload = {
  "amount": 1000,
  "transactionType": "debit",
  "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51",
  "vendor": {
    "softwareName": "MyPOS",
    "softwareVersion": "1.0"
  },
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  },
  "customer": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@yavin.com"
  },
  "acceptedPayment": {
    "acceptedMediumType": "all"
  },
  "checkoutExternalId": "6843z9fgved8",
  "externalTableNumber": "54",
  "externalOrderNumber": "642558",
  "externalOrderId": "14763642558"
}

response = requests.post(
    "http://<LOCAL_IP>:16125/localapi/v4/payment",
    headers=headers,
    json=payload,
    timeout=125,
)
response.raise_for_status()
data = response.json()
```

#### Response

_JSON_
```json
{
  "amount": 1000,
  "appVersion": "3.2.8",
  "cardToken": "1234567890",
  "clientCardTicket": "...",
  "currencyCode": "EUR",
  "giftAmount": 0,
  "scheme": "CB",
  "status": "ok",
  "transactionId": "xPUyi4fmdibD",
  "transactionType": "debit"
}
```

### Start a refund transaction

`POST http://<LOCAL_IP>:16125/localapi/v4/payment`

Credits funds back to the customer card, on the same /payment endpoint, with transactionType set to refund. There are no optional screens, so the recommended client-side timeout is 62 seconds.

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `amount` | Integer | yes |  | Amount in cents to credit back, must be positive |
| `transactionType` | String | yes |  | Must be set to refund. Omitting it, or sending any other value such as credit, starts a debit |
| `idempotentUuid` | String | no | null | Unique identifier of the refund attempt. If the terminal knows a transaction with this UUID (last 24h), it returns that transaction instead of starting a new one. A ko transaction is returned as is: generate a new UUID to retry. If you do not provide one, Yavin generates one internally, which does not protect your POS from double submission |
| `cartId` | String | no | null | Your order number, shown in the MyYavin backoffice to link a transaction to an order |
| `customer` | Customer | no |  | Customer information attached to the transaction |
| `enableGiftScreen` | Boolean | no | null | No effect on refunds: optional screens (tips, reference, review) are disabled |
| `giftAmount` | Integer | no | 0 | Tip or donation in cents, if the tip was selected on the POS side |
| `receiptTicket` | ReceiptTicket | no |  | Receipt content printed together with the card ticket |
| `reference` | String | no | null | Waiter or person processing the transaction |
| `vendor` | Vendor | no |  | Your software name and version, useful for support and debugging |
| `checkoutExternalId` | String | no | null | Merchant-unique checkout ID, required when any external order field below is sent. It must be unique: a request is rejected if a completed ok transaction already uses this value, so send a different value for each transaction of a split payment |
| `externalTableNumber` | String | no | null | Table number. Only used with checkoutExternalId |
| `externalOrderNumber` | String | no | null | Human-readable order number shown to merchant and customer. Only used with checkoutExternalId |
| `externalOrderId` | String | no | null | Unique technical order ID from your POS, for reconciliation. Only used with checkoutExternalId |

#### Request

_JSON_
```json
{
  "amount": 1000,
  "transactionType": "refund",
  "idempotentUuid": "8a4d1f0b-2e57-4c1a-b3d9-1c7e5f2a6d40",
  "vendor": {
    "softwareName": "MyPOS",
    "softwareVersion": "1.0"
  },
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  }
}
```

_cURL_
```bash
curl -X POST 'http://<LOCAL_IP>:16125/localapi/v4/payment' \
  -H 'Content-Type: application/json' \
  -d '{
  "amount": 1000,
  "transactionType": "refund",
  "idempotentUuid": "8a4d1f0b-2e57-4c1a-b3d9-1c7e5f2a6d40",
  "vendor": {
    "softwareName": "MyPOS",
    "softwareVersion": "1.0"
  },
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  }
}'
```

_Node_
```javascript
(async () => {
  const response = await fetch("http://<LOCAL_IP>:16125/localapi/v4/payment", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "amount": 1000,
      "transactionType": "refund",
      "idempotentUuid": "8a4d1f0b-2e57-4c1a-b3d9-1c7e5f2a6d40",
      "vendor": {
        "softwareName": "MyPOS",
        "softwareVersion": "1.0"
      },
      "receiptTicket": {
        "data": "This is the receipt ticket\nto print",
        "format": "text"
      }
    }),
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import requests

headers = {
    "Content-Type": "application/json",
}

payload = {
  "amount": 1000,
  "transactionType": "refund",
  "idempotentUuid": "8a4d1f0b-2e57-4c1a-b3d9-1c7e5f2a6d40",
  "vendor": {
    "softwareName": "MyPOS",
    "softwareVersion": "1.0"
  },
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  }
}

response = requests.post(
    "http://<LOCAL_IP>:16125/localapi/v4/payment",
    headers=headers,
    json=payload,
    timeout=125,
)
response.raise_for_status()
data = response.json()
```

#### Response

_JSON_
```json
{
  "amount": 1000,
  "appVersion": "3.2.8",
  "clientCardTicket": "...",
  "currencyCode": "EUR",
  "giftAmount": 0,
  "scheme": "CB",
  "status": "ok",
  "transactionId": "kQm2Vw9pLxTc",
  "transactionType": "refund"
}
```

### Print a receipt ticket

`POST http://<LOCAL_IP>:16125/localapi/v4/print`

Prints free content on the terminal (invoice, receipt, kitchen slip), as plain text (text) or as ESC/POS commands (escpos).

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `data` | String | yes | null | Content to print |
| `format` | String | no | text | text or escpos |

#### Request

_JSON_
```json
{
  "data": "This is the text\nto print",
  "format": "text"
}
```

_cURL_
```bash
curl -X POST 'http://<LOCAL_IP>:16125/localapi/v4/print' \
  -H 'Content-Type: application/json' \
  -d '{
  "data": "This is the text\nto print",
  "format": "text"
}'
```

_Node_
```javascript
(async () => {
  const response = await fetch("http://<LOCAL_IP>:16125/localapi/v4/print", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "data": "This is the text\nto print",
      "format": "text"
    }),
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import requests

headers = {
    "Content-Type": "application/json",
}

payload = {
  "data": "This is the text\nto print",
  "format": "text"
}

response = requests.post(
    "http://<LOCAL_IP>:16125/localapi/v4/print",
    headers=headers,
    json=payload,
    timeout=125,
)
response.raise_for_status()
data = response.json()
```

#### Response

status = ok means the print request was accepted, not that the receipt was printed: a terminal that cannot print also answers ok. On failure, status = ko with one of:

- Parameter 'data' is missing or empty: empty or missing data field

- Wrong parameter 'format': format is not text or escpos

---

### Share a receipt ticket

`POST http://<LOCAL_IP>:16125/localapi/v4/share-receipt`

Shares a receipt by SMS, email or print. The terminal prompts for the recipient information (phone number or email).

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `receiptTicket` | ReceiptTicket | yes |  | Receipt to share |
| `transactionId` | String | yes |  | Transaction linked to the receipt |
| `medium` | String | yes |  | One of yavin, sms, email, print, in lowercase (SMS is rejected). With yavin, the receipt is stored and shared according to the user notification preferences |

#### Request

_JSON_
```json
{
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  },
  "transactionId": "fsOv53g7wxZ",
  "medium": "email"
}
```

_cURL_
```bash
curl -X POST 'http://<LOCAL_IP>:16125/localapi/v4/share-receipt' \
  -H 'Content-Type: application/json' \
  -d '{
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  },
  "transactionId": "fsOv53g7wxZ",
  "medium": "email"
}'
```

_Node_
```javascript
(async () => {
  const response = await fetch("http://<LOCAL_IP>:16125/localapi/v4/share-receipt", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "receiptTicket": {
        "data": "This is the receipt ticket\nto print",
        "format": "text"
      },
      "transactionId": "fsOv53g7wxZ",
      "medium": "email"
    }),
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import requests

headers = {
    "Content-Type": "application/json",
}

payload = {
  "receiptTicket": {
    "data": "This is the receipt ticket\nto print",
    "format": "text"
  },
  "transactionId": "fsOv53g7wxZ",
  "medium": "email"
}

response = requests.post(
    "http://<LOCAL_IP>:16125/localapi/v4/share-receipt",
    headers=headers,
    json=payload,
    timeout=125,
)
response.raise_for_status()
data = response.json()
```

#### Response

status = ok (with an optional message) when the receipt was shared. On failure, status = ko with one of:

- Parameter 'transactionId' is missing or invalid: transactionId null or not found

- Parameter 'receiptTicket' is missing or invalid: receiptTicket null, data null, or format is not text

- Parameter 'medium' is missing or invalid: medium is missing, or is not print, email, sms or yavin in lowercase

- Error: bad request: request badly formatted

---

### Reverse a transaction

`POST http://<LOCAL_IP>:16125/localapi/v4/reversal`

Reverses a recent transaction.

> ⚠️ Reversal conditions. The original transaction is an ok debit processed on the same terminal issuing the reversal, less than 16 hours ago (terminal time), and the reversal amount is equal to the original total amount, tip included (amount + giftAmount).

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `amount` | Integer | yes |  | Amount in cents, must equal the original total amount (amount  • giftAmount) |
| `initialTransactionId` | String | yes |  | Transaction to reverse |

#### Request

_JSON_
```json
{
  "amount": 1000,
  "initialTransactionId": "xPUyi4fmdibD"
}
```

_cURL_
```bash
curl -X POST 'http://<LOCAL_IP>:16125/localapi/v4/reversal' \
  -H 'Content-Type: application/json' \
  -d '{
  "amount": 1000,
  "initialTransactionId": "xPUyi4fmdibD"
}'
```

_Node_
```javascript
(async () => {
  const response = await fetch("http://<LOCAL_IP>:16125/localapi/v4/reversal", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "amount": 1000,
      "initialTransactionId": "xPUyi4fmdibD"
    }),
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import requests

headers = {
    "Content-Type": "application/json",
}

payload = {
  "amount": 1000,
  "initialTransactionId": "xPUyi4fmdibD"
}

response = requests.post(
    "http://<LOCAL_IP>:16125/localapi/v4/reversal",
    headers=headers,
    json=payload,
    timeout=125,
)
response.raise_for_status()
data = response.json()
```

#### Response

status = ok with reversalTransactionId (the new reversal transaction) and initialTransactionId (the reversed one). On failure, status = ko with one of the following messages, always prefixed with Error : :

- Error : Transaction ID is mandatory: initialTransactionId missing

- Error : Transaction not found for ID: <transactionId>: unknown transaction on this terminal

- Error : Amount mismatch: amount differs from the original total amount (amount + giftAmount)

- Error : Transaction has already been reversed

- Error : Cannot reverse this Transaction or time window expired: the transaction is not an ok debit or VAD transaction, or is more than 16 hours old

- Error : No suitable gateway found to handle reversal

- Error : An unexpected error occurred. Please try again later.: generic error

---

### Abort a transaction

`POST http://<LOCAL_IP>:16125/localapi/v4/abort`

Aborts the ongoing payment: every payment screen is closed and the terminal returns to its main screen.

> ⚠️ Avoid aborting during the card reading phase. The payment gateway screen may close unexpectedly and the payment information may not be transmitted properly, leading to data inconsistencies.

When the abort can act. The abort acts from the moment the payment request is received until the terminal hands over to the banking screen. On a payment without optional screens (tips, reference, review), this handover happens within about 1 to 2 seconds after the /payment call. Past that point the abort can no longer interrupt the payment, even if no card has been presented yet: the customer can still present a card and complete the payment at the requested amount.

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `idempotentUuid` | String | no | null | UUID of the payment attempt to abort |

#### Request

_JSON_
```json
{
  "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51"
}
```

_cURL_
```bash
curl -X POST 'http://<LOCAL_IP>:16125/localapi/v4/abort' \
  -H 'Content-Type: application/json' \
  -d '{
  "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51"
}'
```

_Node_
```javascript
(async () => {
  const response = await fetch("http://<LOCAL_IP>:16125/localapi/v4/abort", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51"
    }),
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import requests

headers = {
    "Content-Type": "application/json",
}

payload = {
  "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51"
}

response = requests.post(
    "http://<LOCAL_IP>:16125/localapi/v4/abort",
    headers=headers,
    json=payload,
    timeout=125,
)
response.raise_for_status()
data = response.json()
```

#### Response

status = ok when the transaction is aborted.

The response is sent once the outcome is known: if the payment can no longer be interrupted, it only arrives after the payment completes, which can take tens of seconds while the terminal waits for the card and shows the result screen.

On failure (transaction still ongoing or nothing to abort), status = ko with one of:

- Abort not possible: the payment could not be interrupted and ran to completion. The response to the initial /payment is then authoritative: a status = ok there means the customer has been charged

- Nothing to abort: no payment is in progress

- Abort is not allowed: the ongoing payment was not started through the Local API

- A transaction with the given idempotentUuid is already in progress (in the actual message, idempotentUuid is wrapped in backticks): the request is trying to abort a different transaction

- Error while responding to local API: <cause>: Yavin Pay internal error

> ⚠️ After requesting an abort. Always wait for the abort response before treating the payment as cancelled. On ko, wait for the response to the initial /payment and process it: on ok the customer has been charged, and the order must account for it or a refund must be triggered. Do not start a new payment for the same order before that response arrives: it would be rejected with A transaction with the given idempotentUuid is already in progress.

---

## Other

### The Transaction object

Returned by the payment endpoints. Fields coming from your request are echoed back as is. Keys whose value is null are omitted: tolerate missing keys. A declined payment has status = ko and no message field.

```json
{
  "status": "ok",
  "transactionId": "xPUyi4fmdibD",
  "amount": 1000,
  "giftAmount": 0,
  "currencyCode": "EUR",
  "transactionType": "debit",
  "appVersion": "3.2.8",
  "cardToken": "1234567890",
  "clientCardTicket": "...",
  "merchantCardTicket": "...",
  "paymentApplication": "EMV_CONTACTLESS",
  "scheme": "CB",
  "issuer": "VISA",
  "idempotentUuid": "2f1c2e2a-6c21-4a4b-9c2b-9f6f2c8e9b51",
  "cartId": "ORDER-2026-000123",
  "reference": "Luke",
  "customer": {
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@yavin.com"
  },
  "checkoutExternalId": "6843z9fgved8",
  "externalTableNumber": "54",
  "externalOrderNumber": "642558",
  "externalOrderId": "14763642558"
}
```

#### Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| status | String | ok or ko |
| transactionId | String | Server-side identifier |
| amount | Integer | Amount in cents, excluding giftAmount. The customer is debited the sum of amount and giftAmount (equal to total_amount in the webhook payload) |
| giftAmount | Integer | Tip or donation in cents |
| currencyCode | String | ISO 4217 code |
| transactionType | String | Lowercase: debit or refund. Tolerate other values (eg na) |
| appVersion | String | Yavin Pay app version |
| cardToken | String | Unique token of the customer card |
| clientCardTicket / merchantCardTicket | String | Customer and merchant card tickets |
| paymentApplication | String | AMEX, ANCV, CONECS_CONTACT, CONECS_CONTACTLESS, EMV_CONTACT, EMV_CONTACTLESS, EMV_MOTO, EMV_PAYMENT_LINK, RESTOFLASH, DISCOVER, CUP |
| scheme | String | Acceptance network (eg VISA) |
| issuer | String | Card issuer (eg CB, VISA, MASTERCARD) |
| idempotentUuid | String | UUID from the request |
| cartId, reference, customer |  | Echoed from the request |
| checkoutExternalId, externalTableNumber, externalOrderNumber, externalOrderId | String | Echoed from the request |

---

### The Customer object

Optional on /payment. Customer information attached to the transaction and echoed back in the response. It is not used by /share-receipt, where the terminal always prompts for the recipient.

```json
{
  "customer": {
    "firstName": "John",
    "lastName": "Doe",
    "phone": "+33612345678",
    "email": "john@yavin.com"
  }
}
```

#### Attributes

| Attribute | Type | Required | Description |
| --- | --- | --- | --- |
| firstName | String | no | First name |
| lastName | String | no | Last name |
| email | String | no | Email |
| phone | String | no | International format starting with + (eg +33612345678) |
| birthDate | String | no | Birthdate |

---

### The ReceiptTicket object

Content printed alongside the card ticket on /payment, or shared on /share-receipt.

```json
{
  "receiptTicket": {
    "data": "Receipt ticket here to print if needed",
    "format": "text"
  }
}
```

#### Attributes

| Attribute | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| data | String | yes |  | Content to print |
| format | String | no | text | Format of the content |
| tax_breakdown | Array | no |  | Array of Tax objects, one per tax rate |

---

### The Tax object

Optional breakdown attached to a ReceiptTicket, under the tax_breakdown key, with one entry per tax rate.

> ⚠️ The key is tax_breakdown (not tax), and its entries are in snake_case.

```json
{
  "tax_breakdown": [
    { "tax_amount": 167, "tax_percentage": 20 },
    { "tax_amount": 50, "tax_percentage": 10 }
  ]
}
```

#### Attributes

| Attribute | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| tax_amount | Integer | no | null | Calculated tax amount in cents, from tax_percentage applied to the transaction amount |
| tax_percentage | Integer | no | null | Percentage applied to the amount (eg 20 for 20%) |

---

### The Vendor object

Identifies your POS software. Optional everywhere, but strongly recommended: it is what lets our support team trace an issue back to a specific integration and version.

```json
{
  "vendor": {
    "softwareName": "MyPOS",
    "softwareVersion": "1.0"
  }
}
```

#### Attributes

| Attribute | Type | Required | Description |
| --- | --- | --- | --- |
| softwareName | String | no | Name of the POS software |
| softwareVersion | String | no | Version of the POS software, helps track and identify bugs quickly |

---

### The AcceptedPayment object

Optional on /payment for debit transactions. Restricts the payment methods the terminal accepts: the customer cannot pay with an excluded method. Typical use: split payments where a first transaction is paid in meal vouchers only and a second transaction covers the remainder in bank card only. If you send a checkoutExternalId, use a different value for each transaction: a value already used by an ok transaction is rejected. Note that the terminal never splits a payment on its own: if the requested amount exceeds the card limit (eg the meal voucher cap), the transaction is fully declined (status ko), there is no partial approval.

```json
{
  "acceptedPayment": {
    "acceptedMediumType": "lunch_vouchers_only"
  }
}
```

#### Attributes

| Attribute | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| acceptedMediumType | String | no | all | all: every payment method configured on the terminal. lunch_vouchers_only: meal voucher cards only. bank_cards_only: bank cards only |

---

### Related pages

[IdempotentUuid Management](https://app.notion.com/p/3bc9a8f4fd9a81d6a3cbfce4939ac0e9)

[Add on : Protocole Bonjour](/p/3bc9a8f4fd9a8198a397cff4e0940c72)

[Add on : Security, signature and HTTPS](/p/3bc9a8f4fd9a810b8dd6e87039cab666)
