Process delegated refunds

Ask about this Page
Copy for LLM
View as Markdown

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.
For delegated credit and stored value (GiftCard) refunds, see Credit and stored value refund processing. InStore records the tender against the session. It does not create or reverse a commercetools 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.

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 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.
Sample refund objectjson
{
  "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
        }
      ]
    }
  ]
}
ElementData typeDescription
returnCartObjectA reference to the Cart created based on the items refunded. InStore uses this to calculate totals and to get information for the refund receipt.
returnOrdersArray<Object>The Orders that contain the items being returned.
returnOrders[].orderObjectA reference to the Order related to the returned Line Items. Contains typeId and key.
returnOrders[].returnLineItemArray<Object>The Line Items from the Order that are being returned.
refundsArray<Object>The refund instructions to be processed.
refunds[].refundAmountObjectThe refund amount, as { amount, currency }.
refunds[].paymentOptionObjectThe payment option selected for the refund. Contains typeId and key.
refunds[].transactionIdStringThe identifier of the original payment transaction to refund against. A commercetools Payment can carry multiple prior refunds, so this tells InStore which transaction to refund.
refunds[].orderObjectA reference to the Order associated with this refund.
refunds[].originalCTPaymentObjectA reference to the original commercetools Payment used for the purchase.
refunds[].pspDataObjectPayment service provider data related to the refund. Can have any value.
refunds[].metaDataObjectDisplay metadata used to communicate refund information to the user.
refunds[].metaData.promptStringThe main prompt message shown for the refund.
refunds[].metaData.promptInfoStringAdditional prompt information, such as masked card details and the refund amount.
refunds[].fallbackRefundTypesArray<Object>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[].processingSequenceFloat (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.

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. If rounding applies, InStore adds a second transaction for the rounding adjustment. A cash tender still appears in the 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: sent once the refund is finalized, so you can update your own systems.
  2. Refund receipt data webhook: sent when InStore needs data to print the refund receipt.
Implement handlers for both alongside your other payment extension endpoints.

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. In addition to X-Signature and X-Correlation-Id, this request also includes an x-tenant-id header that identifies the tenant.
Sample refund webhook payloadjson
{
  "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 }
        }
      }
    ]
  }
}
ElementData typeDescription
actionStringAlways Refund for this notification. Branch your webhook handler on this value alongside the payment, refund, and receipt actions you already handle.
data.refundSessionIdStringThe internal identifier of the InStore refund session that was finalized.
data.refundTransactionsArray<Object>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[].paymentOptionObjectA reference to the payment option the tender was recorded against. Contains typeId and id.
data.refundTransactions[].amountObjectThe tender amount: { currencyCode, centAmount, fractionDigits }.
data.refundTransactions[].originalRefundTransactionIdStringThe 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[].tenderInformationObjectTender-specific data for the tender being refunded. Shape depends on the tender type.
data.refundTransactions[].tenderInformation.typeStringThe tender type: Cash, Credit, GiftCard (stored value), or Pay on Account (POA), a business account paid by purchase order.
data.refundTransactions[].tenderInformation.tenderItemIdStringThe internal identifier of the InStore tender. Present for every tender type.
data.refundTransactions[].tenderInformation.transactionIdStringCredit only. The payment provider or terminal transaction identifier for the refund.
data.refundTransactions[].tenderInformation.paymentObjectCredit and POA only. A reference to the commercetools Payment associated with the tender. Contains typeId and id.
data.refundTransactions[].tenderInformation.roundedRemainderObjectCash 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.accountObjectPOA 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 or Transaction. Your payment processor integration remains responsible for the reversal. For a cash tender, InStore adds the refund transaction to the commercetools Payment before sending this webhook. For more information, see 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 signs this request the same way it signs other webhook requests. In addition to X-Signature and X-Correlation-Id, this request also includes the following headers:
NamePurpose
X-Api-KeyA key to authenticate the InStore API call to your webhook service.
x-tenant-idIdentifies the tenant that is being called.
Sample refund receipt data request payloadjson
{
  "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"
      }
    ]
  }
}
ElementData typeDescription
actionStringAlways RefundReceiptDataRetrieval for this request.
data.refundSessionObjectThe 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.refundSessionIdStringThe internal identifier of the InStore refund session.
data.refundSession.returnCartObjectA reference to the Cart created for the returned items. Contains typeId, key, and id.
data.refundSession.returnOrdersArray<Object>The Orders and Line Items being returned, each with order (a reference) and returnLineItem (an array of references).
data.refundSession.refundsArray<Object>The refund instructions for the session. Includes refundAmount, paymentOption, order, originalCTPayment, metaData (prompt and promptInfo), and fallbackRefundTypes.
data.refundSession.statusStringThe refund session's current status, for example Refunded.
data.tenderItemsArray<Object>Every tender recorded against the session, in the raw shape InStore persists it.
data.tenderItems[].tender_typeStringThe tender type, such as Cash, Credit, or GiftCard.
data.tenderItems[].amountObjectThe tender amount: { amount, currency, precision }.
data.tenderItems[].cashObjectCash tenders only. The cash-rounding adjustment applied to the tender: { roundedRemainder: { amount, currency, precision } }.
data.tenderItems[].reference / original_reference / payment_id / payment_option_idStringIdentifiers 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.

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. 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.
Sample refund receipt data responsejson
{
  "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"
    }
  ]
}
ElementData typeDescription
order_numberStringThe Order or transaction number, printed on the receipt and encoded in its barcode.
timestamp_formattedStringThe refund date and time, pre-formatted for display.
workstation_idStringThe workstation where the return was processed.
user_idStringOptional. The associate who processed the return. Omit to leave the cashier line blank.
tax_idStringOptional. The tax identifier to print on the receipt. Omit to leave the VAT ID line blank.
reprintBooleanOptional. 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.
locationObjectThe 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.
totalObjectThe total amount refunded, as commercetools money. Printed next to Refund: on the receipt.
cartsArray<Object>One entry per returned Cart. Each entry includes line_items, and optionally price_modifiers and attributes per Line Item.
refundsArray<Object>One entry per refund tender. Each entry includes type and amount, plus the tender-specific fields below.
refunds[].cashObjectCash tenders only. { roundedRemainder }. Omit when the refund needed no rounding.
refunds[].charge_cardObjectCredit tenders only. Card and processor receipt data, such as last4, brand, and the additional_data.receiptData fields returned by your processor.
refunds[].gift_cardObjectStored value (GiftCard) tenders only. { last4, type }.
refunds[].voucherObjectVoucher tenders only. { number }.
refunds[].walletObjectWallet tenders only. { method, transaction_id, merchant_id, terminal_id }.
tax_summaryArray<Object>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:

ElementData typeDescription
refunds[].paymentObjectInformation about the original payment method being refunded: { paymentMethodInfo: { paymentInterface, method, name } }.
refunds[].reference / originalReferenceStringIdentifiers for the return transaction and the original transaction it refunds.
refundSessionObjectThe refund session's status and returnOrders, echoing values from the request InStore sent you.
workstationObjectA fuller workstation reference than workstation_id alone, if you resolve one.
originalPaymentsArray<Object>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.