Make a refund
Refund a previous payment
The ePOS app's interaction with POSLink consists of a single call to POST a refund request.
Given a payment request took place in the past, the ePOS should have stored the original gateway_payment_id returned when the payment completed successfully.
When the merchant is ready to start the refund, they will then trigger a call to POSLink and create a refund request with the original gateway_payment_id and amount. A response with the status of the refund will happen synchronously.
The refund request carries that identifier in a field named transaction_id. The field name and the identifier name differ: the value you send is the gateway_payment_id.
Refunds use the V2 endpoint POST /poslink/v2/refunds, which identifies the original payment by its gateway_payment_id, sent in the transaction_id field.
Do not send the deprecated transaction_id from the V1 payment request response. Despite the matching field name it is a different identifier, and the refund will fail with 404 TRANSACTION_NOT_FOUND.
The refund amount follows ISO 4217 minor units for every currency, including HUF: send 250000 to refund 2500 Ft. This differs from payment requests and unreferenced refunds.
POST /poslink/v2/refunds HTTP/1.1
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>
Idempotency-Key: 9f1c2e7a-3b4d-4e5f-8a9b-0c1d2e3f4a5b
Host: api.teya.com
{
"transaction_id": "39e3bacdd5d38d9ce611cf88afbcc86df829a9a5641880b3",
"amount": 1500,
"currency": "GBP",
"merchant_reference": "order-12345"
}
The Idempotency-Key header is optional but recommended: repeating a request with the same key returns the stored refund with its current status instead of creating a duplicate refund, which makes retries safe.
There is no endpoint to fetch a refund later. Repeating the request with the same Idempotency-Key and body is the only way to get the latest result, so always send one. Repeat the request when:
- the response is
201withstatusPENDING - the response is
400with codeUNKNOWN, which can mean a refund with the same key is still being processed
Without an Idempotency-Key, every request creates a new refund.
{
"transaction_id": "tr_2b9d1f4c-5e6a-4b7c-8d9e-0f1a2b3c4d5e",
"refund_amount": {
"amount": 1500,
"currency": "GBP"
},
"status": "SUCCESS",
"transaction_type": "REFUND",
"card_acceptor_id": "5023045",
"issuer_result": {
"approval_code": "123456",
"response_code": "00"
},
"created_at": "2022-06-15T16:26:12.000Z"
}
When a refund is declined, status is FAILURE and a status_reason field (for example INSUFFICIENT_FUNDS or DO_NOT_HONOR) describes why. For Dynamic Currency Conversion refunds, the response also includes a dcc object with the cardholder amount and conversion rate.
Refund without an original sale (Unreferenced Refund)
An unreferenced refund returns money to a customer when there is no earlier payment to refund against - for example, a goodwill refund, or a return where the original sale was taken on a different system. It is a standalone, card-present transaction.
Unreferenced refunds is currently not open to new applications. You may implement and test but not board any live customers.
Referenced vs unreferenced refunds - which to use
Referenced refunds are strongly recommended wherever possible.
An unreferenced refund sends a credit to a card without any link to a prior payment. Because there is no original transaction to validate against, these refunds are subject to additional fraud screening. As a result:
- Some merchants may not be eligible to process unreferenced refunds at all.
- An individual unreferenced refund may be declined even where the same refund would succeed as a referenced refund.
- Eligibility cannot be reliably predicted, so building exclusively on unreferenced refunds risks unexplained refusals for merchants.
Use unreferenced refunds only where the original gateway_payment_id is genuinely unavailable.
Unlike a referenced refund, an unreferenced refund does not point to an earlier transaction. It runs at the terminal like a card payment in reverse: the customer presents their card and the money is returned to it.
Because the customer must be present with their card, an unreferenced refund follows the same three steps as a card payment:
- The ePOS creates the refund, and POSLink sends it to the terminal.
- The customer taps or inserts their card.
- The ePOS follows the refund's status until it reaches a final state -
SUCCESSFUL,FAILED, orCANCELLED.
An unreferenced refund uses the same flow as a card payment (see Send a payment request). The ePOS calls create a payment request with the type set to a refund, and follows its status in the same way until it completes.
The request is identical to a card payment except for transaction_type, which is set to REFUND:
POST /poslink/v3/payment-requests HTTP/1.1
Content-Type: application/json
Authorization: Bearer <ACCESS_TOKEN>
Idempotency-Key: 9f1c2e7a-3b4d-4e5f-8a9b-0c1d2e3f4a5b
Host: api.teya.com
{
"store_id": "eac4d470-4731-45b2-af6b-c828f03c1f9d",
"terminal_id": "70dc65ba-1c13-492a-82a0-15821e5f63ba",
"requested_amount": {
"amount": 1500,
"currency": "GBP",
"tip": 0
},
"transaction_type": "REFUND",
"merchant_reference": "REFUND_ORDER_123",
"epos_instance_id": "eposName_1"
}
POSLink creates the refund with status NEW and sends it to the terminal. Its status is then followed the same way as a payment until it reaches a final state:
{
"payment_request_id": "9b2c1d7e-4a3f-4c8b-9e2d-1f0a6b5c4d3e",
"requested_amount": {
"currency": "GBP",
"amount": 1500,
"tip": 0
},
"status": "NEW",
"transaction_type": "REFUND",
"merchant_reference": "REFUND_ORDER_123",
"terminal_id": "70dc65ba-1c13-492a-82a0-15821e5f63ba",
"store_id": "eac4d470-4731-45b2-af6b-c828f03c1f9d",
"epos_instance_id": "eposName_1",
"created_at": "2026-04-21T10:05:00Z",
"updated_at": "2026-04-21T10:05:00Z"
}
Good to know
- The refund amount is set by the ePOS; there is no original payment to validate it against.
- Amounts follow the payment request rules, so HUF is currently treated without minor units: send
2500to refund 2500 Ft, not250000. This differs from refunding a previous payment, where HUF uses ISO 4217 minor units. - The refund returns the full
amount, which is inclusive of anytip- thetipvalue does not change the amount returned.tip: 0is recommended for a refund, and anytipsent must be less thanamount, otherwise the request is rejected. - The customer must present their card at the terminal; an unreferenced refund cannot be performed remotely.
- Unreferenced refunds are not available for Pay at Table, which supports sales only.
- If unreferenced refunds are not enabled for the terminal, the refund is declined with
statusFAILEDandstatus_reasonFEATURE_NOT_ACTIVATED_UNREFERENCED_REFUNDS.
Refund validation
When refunding a previous payment, in addition to the ePOS application's own checks, the Teya payment gateway ensures the refund amount does not exceed the original sale amount.
An unreferenced refund has no previous payment to compare against, so this limit does not apply.
Errors
The errors below apply when refunding a previous payment.
| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST |
The request is invalid (e.g. amount not represented in minor units, or a negative amount) |
| 400 | TRANSACTION_NOT_ALLOWED |
The original transaction does not permit a refund |
| 400 | UNKNOWN |
The refund result is not known yet, for example a refund with the same Idempotency-Key is still being processed. Repeat the same request with the same Idempotency-Key |
| 401 | UNAUTHORIZED |
The request lacks valid authentication credentials |
| 403 | FORBIDDEN |
The caller is not permitted to refund this transaction |
| 404 | TRANSACTION_NOT_FOUND |
No transaction was found for the supplied transaction_id. Check you sent the gateway_payment_id of the original payment, not the deprecated V1 transaction_id |
| 409 | CONFLICT |
The refund conflicts with the current state of the transaction (e.g. already refunded or cannot be reversed) |
| 500 | INTERNAL_SERVER_ERROR |
Internal server error |
| 503 | SERVICE_UNAVAILABLE |
Service unavailable |
| 523 | ORIGIN_UNAVAILABLE |
Origin unavailable |