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 201 with status PENDING
  • the response is 400 with code UNKNOWN, 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:

  1. The ePOS creates the refund, and POSLink sends it to the terminal.
  2. The customer taps or inserts their card.
  3. The ePOS follows the refund's status until it reaches a final state - SUCCESSFUL, FAILED, or CANCELLED.

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 2500 to refund 2500 Ft, not 250000. This differs from refunding a previous payment, where HUF uses ISO 4217 minor units.
  • The refund returns the full amount, which is inclusive of any tip - the tip value does not change the amount returned. tip: 0 is recommended for a refund, and any tip sent must be less than amount, 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 status FAILED and status_reason FEATURE_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