# Process delegated refunds Understand how InStore delegates returns and refunds to your integration, and implement the webhook notifications your integration receives. ## Process delegated refunds InStore supports two ways to handle returns and refunds: - **Delegated refunds**: InStore initiates and manages the refund, then notifies your integration through webhooks. Your integration consumes these notifications and updates your own systems. Your payment processor reverses the original charge. Your integration does not post the refund to commercetools or call a refund-session API. - **InStore-managed refunds**: you build refund processors similar to payment processors. InStore posts the refund to commercetools on your behalf using Custom Fields. This topic does not cover that integration path. This topic covers **delegated refunds** only. It pairs with the delegated payments topic. InStore processes returns and refunds through a refund session. A single return can span multiple tenders, such as the original payment method and a fallback tender. You can also define a fallback payment option to use when the original tender type is unavailable for a refund. InStore creates and manages refund sessions internally as a store associate completes a return. You do not call a refund session API directly. Your integration implements the two webhook notifications described below. For an overview of payment types, payment processors, and payment options, see [payment components overview](/instore/integration/payments/overview.md). For delegated credit and stored value (`GiftCard`) refunds, see [Credit and stored value refund processing](/instore/integration/refunds/process-refunds.md#credit-and-stored-value-refund-processing). InStore records the tender against the session. It does not create or reverse a commercetools [Payment](/search.md?urn=ctp:api:type:Payment) transaction on your behalf. Your payment processor integration remains responsible for reversing the original charge. For details on your payment processor's refund endpoint, see [Processor webhook paths that we provide for specific payment types](/instore/implement-instore/custom-payments/payment-extensions.md#processor-webhook-paths-that-we-provide-for-specific-payment-types). Cash tenders work differently. ### Refund object When a store associate starts a return, InStore builds a refund object. It describes the return Cart, the return Orders and Line Items, and one or more refund instructions. Each refund instruction names a payment option and the original commercetools [Payment](/search.md?urn=ctp:api:type:Payment) it refunds. It can also name fallback payment options to try when the primary option cannot complete the refund. InStore builds and sends this object. Your integration never sends it. The object shape is echoed back throughout the webhook notifications described below. ```json title="Sample refund object" { "returnCart": { "typeId": "cart", "key": "cart-1" }, "returnOrders": [ { "order": { "typeId": "order", "key": "order-1" }, "returnLineItem": [{ "typeId": "line-item", "id": "193eb21f-a5e7-4327-a6c9-705282da9f30" }] } ], "refunds": [ { "refundAmount": { "amount": 4999, "currency": "USD" }, "paymentOption": { "typeId": "paymentOption", "key": "option-payment-key" }, "transactionId": "451sd5f1", "order": { "typeId": "order", "key": "order-1" }, "originalCTPayment": { "typeId": "payment", "id": "pay_3Nk82jLkQ4eR1mWx" }, "pspData": { "originalPaymentMethodName": "MasterCard 4444" }, "metaData": { "prompt": "Refund will be made to original payment", "promptInfo": "Mastercard ending 4444: -$50.00" }, "fallbackRefundTypes": [ { "paymentOption": { "typeId": "paymentOption", "key": "option-credit-us-1" }, "pspData": {}, "metaData": { "prompt": "Refund will be made to original payment", "promptInfo": "Mastercard ending 4444: -$50.00" }, "processingSequence": 0.9 } ] } ] } ``` | Element | Data type | Description | | --- | --- | --- | | returnCart | `Object` | A reference to the Cart created based on the items refunded. InStore uses this to calculate totals and to get information for the refund receipt. | | returnOrders | `Array` | The Orders that contain the items being returned. | | returnOrders\[].order | `Object` | A reference to the Order related to the returned Line Items. Contains `typeId` and `key`. | | returnOrders\[].returnLineItem | `Array` | The Line Items from the Order that are being returned. | | refunds | `Array` | The refund instructions to be processed. | | refunds\[].refundAmount | `Object` | The refund amount, as `{ amount, currency }`. | | refunds\[].paymentOption | `Object` | The payment option selected for the refund. Contains `typeId` and `key`. | | refunds\[].transactionId | `String` | The identifier of the original payment transaction to refund against. A commercetools [Payment](/search.md?urn=ctp:api:type:Payment) can carry multiple prior refunds, so this tells InStore which transaction to refund. | | refunds\[].order | `Object` | A reference to the Order associated with this refund. | | refunds\[].originalCTPayment | `Object` | A reference to the original commercetools [Payment](/search.md?urn=ctp:api:type:Payment) used for the purchase. | | refunds\[].pspData | `Object` | Payment service provider data related to the refund. Can have any value. | | refunds\[].metaData | `Object` | Display metadata used to communicate refund information to the user. | | refunds\[].metaData.prompt | `String` | The main prompt message shown for the refund. | | refunds\[].metaData.promptInfo | `String` | Additional prompt information, such as masked card details and the refund amount. | | refunds\[].fallbackRefundTypes | `Array` | Fallback refund options to use if the primary refund option cannot be processed. A refund can use a configured credit option as a fallback. | | refunds\[].fallbackRefundTypes\[].processingSequence | `Float (0-1)` | The priority or order in which to process the fallback refund option. | #### Credit and stored value refund processing Delegated refunds support credit and stored value (`GiftCard`) tender types. InStore communicates with your processor following the same pattern as other card payments. For details, see [Processor webhook paths that we provide for specific payment types](/instore/implement-instore/custom-payments/payment-extensions.md#processor-webhook-paths-that-we-provide-for-specific-payment-types). #### Refund processing for cash Cash tenders use a separate refund process. InStore communicates with your cash processor to reverse the charge. When a return includes a cash tender, InStore adds the refund transaction directly to the referenced commercetools [Payment](/search.md?urn=ctp:api:type:Payment). If rounding applies, InStore adds a second transaction for the rounding adjustment. A cash tender still appears in the [refund webhook](/instore/integration/refunds/process-refunds.md#refund-webhook) alongside any credit or stored value tenders on the same return. Your integration can use this information to keep its own systems in sync. When a store associate finalizes a return, InStore sends your integration two kinds of notifications: 1. [Refund webhook](/instore/integration/refunds/process-refunds.md#refund-webhook): sent once the refund is finalized, so you can update your own systems. 2. [Refund receipt data webhook](/instore/integration/refunds/process-refunds.md#refund-receipt-data-webhook): sent when InStore needs data to print the refund receipt. Implement handlers for both alongside your other [payment extension endpoints](/instore/implement-instore/custom-payments/payment-extensions.md). ### Refund webhook When a refund session is finalized, InStore sends a webhook notification to the webhook URL configured for your tenant. InStore signs this request the same way it signs other webhook requests. For more information about these, see [Header information that you receive in all InStore payment requests](/instore/implement-instore/custom-payments/payment-extensions.md#header-information-that-you-receive-in-all-instore-payment-requests). In addition to `X-Signature` and `X-Correlation-Id`, this request also includes an `x-tenant-id` header that identifies the tenant. ```json title="Sample refund webhook payload" { "tenant": { "type": "tenant", "key": "acme-retail" }, "location": { "type": "location", "key": "01" }, "workstation": { "type": "workstation", "key": "001" }, "action": "Refund", "data": { "refundSessionId": "6a3c08d503353f78bbe2c08f", "refundTransactions": [ { "paymentOption": { "typeId": "paymentOption", "id": "691bb93ff14fd895922ceb16" }, "amount": { "currencyCode": "USD", "centAmount": 4999, "fractionDigits": 2 }, "originalRefundTransactionId": "txn_original_payment_1", "tenderInformation": { "type": "Credit", "tenderItemId": "6a3c091003353f78bbe2c090", "transactionId": "8156515", "payment": { "typeId": "payment", "id": "pay_3Nk82jLkQ4eR1mWx" } } }, { "paymentOption": { "typeId": "paymentOption", "id": "691bb93ff14fd895922ceb17" }, "amount": { "currencyCode": "USD", "centAmount": 500, "fractionDigits": 2 }, "originalRefundTransactionId": "txn_original_payment_1", "tenderInformation": { "type": "Cash", "tenderItemId": "6a3c091003353f78bbe2c091", "roundedRemainder": { "currencyCode": "USD", "centAmount": -1, "fractionDigits": 2 } } } ] } } ``` | Element | Data type | Description | | --- | --- | --- | | action | `String` | Always `Refund` for this notification. Branch your webhook handler on this value alongside the `payment`, `refund`, and `receipt` actions you already handle. | | data.refundSessionId | `String` | The internal identifier of the InStore refund session that was finalized. | | data.refundTransactions | `Array` | One entry per tender that made up the refund. A session can have multiple entries when a return is split between the original payment method and a fallback tender. | | data.refundTransactions\[].paymentOption | `Object` | A reference to the [payment option](/instore/integration/payments/overview.md#payment-options) the tender was recorded against. Contains `typeId` and `id`. | | data.refundTransactions\[].amount | `Object` | The tender amount: `{ currencyCode, centAmount, fractionDigits }`. | | data.refundTransactions\[].originalRefundTransactionId | `String` | The identifier of the original payment transaction that this tender refunds. Use this to match the refund back to the payment you are reversing. | | data.refundTransactions\[].tenderInformation | `Object` | Tender-specific data for the tender being refunded. Shape depends on the tender type. | | data.refundTransactions\[].tenderInformation.type | `String` | The tender type: `Cash`, `Credit`, `GiftCard` (stored value), or Pay on Account (POA), a business account paid by purchase order. | | data.refundTransactions\[].tenderInformation.tenderItemId | `String` | The internal identifier of the InStore tender. Present for every tender type. | | data.refundTransactions\[].tenderInformation.transactionId | `String` | `Credit` only. The payment provider or terminal transaction identifier for the refund. | | data.refundTransactions\[].tenderInformation.payment | `Object` | `Credit` and POA only. A reference to the commercetools [Payment](/search.md?urn=ctp:api:type:Payment) associated with the tender. Contains `typeId` and `id`. | | data.refundTransactions\[].tenderInformation.roundedRemainder | `Object` | `Cash` only. The cash-rounding adjustment applied to the refund, as commercetools money: `{ currencyCode, centAmount, fractionDigits }`. Omitted when the refund needed no rounding. | | data.refundTransactions\[].tenderInformation.account | `Object` | POA only. The business account the tender was charged to: `{ company_name, account_number, authorized_user_id, purchase_order }`. `purchase_order` is optional. | For delegated credit tenders, InStore does not reverse a commercetools [Payment](/search.md?urn=ctp:api:type:Payment) or [Transaction](/search.md?urn=ctp:api:type:Transaction). Your payment processor integration remains responsible for the reversal. For a cash tender, InStore adds the refund transaction to the commercetools [Payment](/search.md?urn=ctp:api:type:Payment) before sending this webhook. For more information, see [Credit and stored value refund processing](/instore/integration/refunds/process-refunds.md#credit-and-stored-value-refund-processing). InStore sends this webhook on a best-effort basis and does not wait for your response before completing the return in the InStore POS. Respond with a `200` status code to acknowledge receipt. If your webhook service doesn't acknowledge the request, InStore retries up to three times using exponential backoff. InStore waits 1 second before the first retry, 2 seconds before the second retry, and 4 seconds before the third retry. Then, InStore stops retrying. This does not affect the finalized refund session. Your integration is responsible for reconciling any missed notifications through your own means. ### Refund receipt data webhook To print a refund receipt, InStore requests data for a finalized refund session from your webhook URL. Unlike the refund webhook, this is a synchronous request. InStore waits for your response and uses it to render the [refund receipt](/instore/implement-instore/receipts/refund-receipt.md). InStore signs this request the same way it signs [other webhook requests](/instore/implement-instore/custom-payments/payment-extensions.md#header-information-that-you-receive-in-all-instore-payment-requests). In addition to `X-Signature` and `X-Correlation-Id`, this request also includes the following headers: | Name | Purpose | | --- | --- | | `X-Api-Key` | A key to authenticate the InStore API call to your webhook service. | | `x-tenant-id` | Identifies the tenant that is being called. | ```json title="Sample refund receipt data request payload" { "tenant": { "typeId": "tenant", "key": "acme-retail" }, "location": { "typeId": "location", "key": "01" }, "workstation": { "typeId": "workstation", "key": "001" }, "action": "RefundReceiptDataRetrieval", "data": { "refundSession": { "refundSessionId": "6a3c08d503353f78bbe2c08f", "returnCart": { "typeId": "cart", "key": "cart-1", "id": "1cda6cae-0ceb-460a-b1f6-f483f750bd81" }, "returnOrders": [ { "order": { "typeId": "order", "key": "order-1" }, "returnLineItem": [{ "typeId": "line-item", "id": "193eb21f-a5e7-4327-a6c9-705282da9f30" }] } ], "refunds": [ { "refundAmount": { "amount": 4999, "currency": "USD" }, "paymentOption": { "typeId": "paymentOption", "key": "option-credit-us-1" }, "order": { "typeId": "order", "key": "order-1" }, "originalCTPayment": { "typeId": "payment", "id": "pay_3Nk82jLkQ4eR1mWx" }, "metaData": { "prompt": "Show1", "promptInfo": "Show2" }, "fallbackRefundTypes": [] } ], "status": "Refunded" }, "tenderItems": [ { "_id": "6a3c091003353f78bbe2c090", "session_id": "6a3c08d503353f78bbe2c08f", "transaction_type": "refund", "tender_type": "Credit", "amount": { "amount": 4999, "currency": "USD", "precision": 2 }, "reference": "cart-1", "payment_id": "pay_3Nk82jLkQ4eR1mWx", "original_reference": "order-1", "payment_option_id": "option-credit-us-1", "created_at": "2026-07-22T10:25:43.194Z" } ] } } ``` | Element | Data type | Description | | --- | --- | --- | | action | `String` | Always `RefundReceiptDataRetrieval` for this request. | | data.refundSession | `Object` | The refund session and its current `status`. It includes the return Cart, return Orders, and refunds that InStore built when the return was initiated. | | data.refundSession.refundSessionId | `String` | The internal identifier of the InStore refund session. | | data.refundSession.returnCart | `Object` | A reference to the Cart created for the returned items. Contains `typeId`, `key`, and `id`. | | data.refundSession.returnOrders | `Array` | The Orders and Line Items being returned, each with `order` (a reference) and `returnLineItem` (an array of references). | | data.refundSession.refunds | `Array` | The refund instructions for the session. Includes `refundAmount`, `paymentOption`, `order`, `originalCTPayment`, `metaData` (`prompt` and `promptInfo`), and `fallbackRefundTypes`. | | data.refundSession.status | `String` | The refund session's current status, for example `Refunded`. | | data.tenderItems | `Array` | Every tender recorded against the session, in the raw shape InStore persists it. | | data.tenderItems\[].tender\_type | `String` | The tender type, such as `Cash`, `Credit`, or `GiftCard`. | | data.tenderItems\[].amount | `Object` | The tender amount: `{ amount, currency, precision }`. | | data.tenderItems\[].cash | `Object` | `Cash` tenders only. The cash-rounding adjustment applied to the tender: `{ roundedRemainder: { amount, currency, precision } }`. | | data.tenderItems\[].reference / original\_reference / payment\_id / payment\_option\_id | `String` | Identifiers for the return Cart, original Order, original Payment, and payment option for this tender. | Your webhook service must respond synchronously with the receipt data InStore uses to print the [refund receipt](/instore/implement-instore/receipts/refund-receipt.md). #### Refund receipt data response Respond with a `2xx` status code within five seconds, and a JSON body containing the receipt data. InStore renders this body directly, and unmodified, into the refund receipt template's placeholders. If your webhook service returns a non-`2xx` status, times out after five seconds, or is unreachable, InStore does not retry the request. The refund session is already finalized, so the return is not affected. The InStore POS shows an empty receipt preview, and the associate can still complete the refund without printing or emailing a receipt. For the full list, see [Refund receipt template](/instore/implement-instore/receipts/refund-receipt.md). Your response must include every applicable field the template references, including `location`, which InStore does not fill in for you on this flow. The receipt logo is loaded into the printer's memory separately and isn't part of this payload. Money values use two different shapes depending on where they originate: - Cart, tax, and total amounts use the commercetools money shape: `{ type, currencyCode, centAmount, fractionDigits }`. - Refund tender amounts use `{ amount, currency, precision }`, the same shape InStore sent you in `data.tenderItems[].amount` on the [request above](/instore/integration/refunds/process-refunds.md#refund-receipt-data-webhook). ```json title="Sample refund receipt data response" { "order_number": "30001401000149", "timestamp_formatted": "24/08/2026, 01:36:48", "workstation_id": "01", "user_id": "jsmith", "tax_id": "12-3456789", "reprint": false, "location": { "description": "Acme Retail - Downtown", "address_line_1": "123 Main St", "address_line_2": "Suite 100", "city": "Springfield", "state": "IL", "postal_code": "62701", "telephone_number": "+1 217-555-0100" }, "total": { "type": "centPrecision", "currencyCode": "GBP", "centAmount": 31216, "fractionDigits": 2 }, "carts": [ { "line_items": [ { "description": "Product Name", "quantity": 1, "price": { "type": "centPrecision", "currencyCode": "GBP", "centAmount": 31216, "fractionDigits": 2 }, "extended_price": { "type": "centPrecision", "currencyCode": "GBP", "centAmount": 31216, "fractionDigits": 2 }, "tax_code": "UK", "price_modifiers": [], "attributes": [{ "label": "brand", "value": "Brand Name" }] } ] } ], "refunds": [ { "type": "Cash", "amount": { "amount": 31216, "currency": "GBP", "precision": 2 }, "cash": { "roundedRemainder": { "amount": 0, "currency": "GBP", "precision": 2 } }, "reference": "30001401000150", "originalReference": "30001401000149", "payment": { "paymentMethodInfo": { "paymentInterface": "Custom", "method": "Credit", "name": { "en-US": "credit,4444,mastercard" } } } } ], "tax_summary": [ { "code": "UK", "tax_rate": 0.2, "total_net": { "type": "centPrecision", "currencyCode": "GBP", "centAmount": 26013, "fractionDigits": 2 }, "total_tax": { "type": "centPrecision", "currencyCode": "GBP", "centAmount": 5203, "fractionDigits": 2 } } ], "refundSession": { "status": "Refunded", "returnOrders": [{ "order": "30001401000149" }] }, "workstation": { "id": "01", "description": "Front Register" }, "originalPayments": [ { "amountPlanned": { "type": "centPrecision", "currencyCode": "GBP", "centAmount": 31216, "fractionDigits": 2 }, "paymentMethodInfo": { "paymentInterface": "Custom", "method": "Credit", "name": { "en-US": "credit,4444,mastercard" } }, "orderNumber": "30001401000149" } ] } ``` | Element | Data type | Description | | --- | --- | --- | | order\_number | `String` | The Order or transaction number, printed on the receipt and encoded in its barcode. | | timestamp\_formatted | `String` | The refund date and time, pre-formatted for display. | | workstation\_id | `String` | The workstation where the return was processed. | | user\_id | `String` | Optional. The associate who processed the return. Omit to leave the cashier line blank. | | tax\_id | `String` | Optional. The tax identifier to print on the receipt. Omit to leave the VAT ID line blank. | | reprint | `Boolean` | Optional. Whether this response is for a reprint rather than the original receipt. Omit or set to `false` for the original receipt; the template only renders the "Reprinted Receipt" banner when this is `true`. | | location | `Object` | The store location the return was processed at: `{ description, address_line_1, address_line_2, city, state, postal_code, telephone_number }`. These should match the values configured on your tenant's Location Information page in the InStore Center. `address_line_2` is optional. | | total | `Object` | The total amount refunded, as commercetools money. Printed next to `Refund:` on the receipt. | | carts | `Array` | One entry per returned Cart. Each entry includes `line_items`, and optionally `price_modifiers` and `attributes` per Line Item. | | refunds | `Array` | One entry per refund tender. Each entry includes `type` and `amount`, plus the tender-specific fields below. | | refunds\[].cash | `Object` | `Cash` tenders only. `{ roundedRemainder }`. Omit when the refund needed no rounding. | | refunds\[].charge\_card | `Object` | `Credit` tenders only. Card and processor receipt data, such as `last4`, `brand`, and the `additional_data.receiptData` fields returned by your processor. | | refunds\[].gift\_card | `Object` | Stored value (`GiftCard`) tenders only. `{ last4, type }`. | | refunds\[].voucher | `Object` | Voucher tenders only. `{ number }`. | | refunds\[].wallet | `Object` | Wallet tenders only. `{ method, transaction_id, merchant_id, terminal_id }`. | | tax\_summary | `Array` | One entry per tax code. Each entry includes `code`, `tax_rate`, `total_net`, and `total_tax`. | The refund receipt template doesn't render the following fields, but you can include them if a custom template you build needs them: | Element | Data type | Description | | --- | --- | --- | | refunds\[].payment | `Object` | Information about the original payment method being refunded: `{ paymentMethodInfo: { paymentInterface, method, name } }`. | | refunds\[].reference / originalReference | `String` | Identifiers for the return transaction and the original transaction it refunds. | | refundSession | `Object` | The refund session's `status` and `returnOrders`, echoing values from the request InStore sent you. | | workstation | `Object` | A fuller workstation reference than `workstation_id` alone, if you resolve one. | | originalPayments | `Array` | The original payment(s) being refunded, including `amountPlanned`, `paymentMethodInfo`, and `orderNumber`. | InStore does not validate the shape of your response. Fields you omit or format incorrectly appear blank on the printed or emailed receipt rather than causing an error. ## Related pages - [Area overview page with navigation](/instore.md) - [Search documentation and API specs](/search.md)