Skip to content

Refund an Order

A refund for an order can be made with a single API request to the POST /refunds endpoint. To refund an order, the following requirements must be met:

  • You are using a payment method which supports refunds. These are:
    • Payment Initiation - EUR only + you must have enabled Bank payment refunds in the Partner System
    • Card Payments, Apple Pay, Google Pay, MobilePay, BLIK, BNPL, Financing - refunds are enabled by default
  • The funds have arrived to the merchant’s settlement account in Montonio. This typically takes 1 business day.

Additionally, the following constraints have been put in place for refunds:

  • The refund can only be made to the original payer
  • The total amount of refunds cannot exceed the grandTotal of the order
  • The minimum amount for a refund is 0.05 €

Complete Example

This is a complete example of how to refund an order. The process is as follows:

  1. (optional) Get the Order to check its availableForRefund amount from GET /orders/:orderUuid
  2. Generate a JWT containing the data for a refund
  3. Sign the JWT with your Secret Key
  4. POST the JWT to the API to create a refund

We use JWT to securely pass the refund data to the API and use the JWT’s signature to verify the integrity of the request.

/**
* We recommend using the jsonwebtoken NPM package to generate
* Json Web Tokens. You can install it with npm:
* > npm install jsonwebtoken
*/
const jwt = require('jsonwebtoken');
const axios = require('axios');
const { randomUUID } = require('crypto');
// For production use https://stargate.montonio.com/api
const MONTONIO_BASE_URL = 'https://sandbox-stargate.montonio.com/api';
const MONTONIO_ACCESS_KEY = 'YOUR_ACCESS_KEY';
const MONTONIO_SECRET_KEY = 'YOUR_SECRET_KEY';
const MONTONIO_ORDER_UUID = '12228dce-2f7c-4db5-8d28-5d82a19aa3b6';
// 1. Compose the data for the refund
// We suggest saving the idempotency key in your database to prevent duplicate refunds
const idempotencyKey = randomUUID();
const payload = {
"accessKey": MONTONIO_ACCESS_KEY,
"orderUuid": MONTONIO_ORDER_UUID,
"amount": 1.00, // Please make sure to round the amount to 2 decimal places
"idempotencyKey": idempotencyKey
}
// 2. Generate the token and sign it with your Secret Key
const token = jwt.sign(
payload,
MONTONIO_SECRET_KEY,
{ algorithm: 'HS256', expiresIn: '10m' }
);
// 3. Make the request
axios.post(MONTONIO_BASE_URL + '/refunds', {
data: token
}).then(response => {
console.log(response.data);
}).catch(error => {
console.log(error.response.data);
});

Now let’s break it down into step-by-step instructions.

1. Token Contents

First, you need to compose the refund data for the JWT (JSON Web Token) payload.

Token payload

KeyRequiredTypeDescription
accessKeyyesstringYour Access Key obtained from the Partner System.
orderUuidyesstringThe UUID of the order.
amountyesnumberThe refundable amount up to 2 decimal places (e.g 19.99).
idempotencyKeyyesstringA unique key that you generate for each refund request. This key is used to recognize subsequent retries of the same request. How you generate the keys is up to you but we recommend using V4 UUIDs.
iatyesnumberThe timestamp of when the token was generated. The value must be in seconds since the Unix Epoch. (e.g. 1675860045)
expyesnumberThe timestamp of when the token expires. The value must be in seconds since the Unix Epoch. We recommend setting that to 10 minutes from the time of issuing the token. (e.g. 1675860645)

2. Signing the JWT

Now, you need to generate and sign a JWT (JSON Web Token) using your Secret Key obtained from the Partner System.

The exact implementation of how to generate the JWT varies by programming language and you can see some examples in the Complete Example section.

  • The payload of the JWT is the object described in 1. Token Contents.
  • The JWT is signed with your Secret Key using HMAC SHA256 (HS256).
  • The headers of the JWT must contain the alg and typ keys. They are described below.

Token headers

KeyRequiredTypeDescription
algyesstringMust be set to HS256
typyesstringMust be set to JWT

We recommend using popular community maintained libraries for JWT generation. You can browse the libraries for your programming language on the jwt.io website.

3. Submitting the Token

After you have generated the token, you can submit it to Montonio’s API to refund the order. The API endpoint is:

Terminal window
POST /refunds

The payload is a JSON string in the following format:

{
"data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2Nlc3NLZXkiOiJZT1VSX0FDQ0VTU19LRVkiLCJvcmRlclV1aWQiOiIxMjIyOGRjZS0yZjdjLTRkYjUtOGQyOC01ZDgyYTE5YWEzYjYiLCJpZGVtcG90ZW5jeUtleSI6IjI4NWIwMDc5LTQwODAtNGJmNC05MTQ4LThjMzUyZTJjN2Y4NyIsImFtb3VudCI6MjV9.G5vVKe256PsZ-_6hHCt2FA-lC_XN2CAp_Q9wN1RTWG8"
}

Example response:

{
"uuid": "97b20084-319a-4cce-92f5-56d3b41a986a",
"amount": 25,
"status": "PENDING",
"currency": "EUR",
"createdAt": "2023-05-23T08:37:55.534Z",
"type": "PARTIAL_REFUND"
}

Some exceptions that can be thrown by the API:

CodeDescription
400Order uuid [xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx] already has a refund with same idempotency key
400Refund amount [1000] exceeds the total amount refundable [10]
400amount is under the min allowed amount: 0.05EUR
401STORE_NOT_FOUND - double check your access key
403INVALID_TOKEN - double check your secret key

After the refund is successfully created, you can check its status by calling the GET /orders/:orderUuid endpoint. The refund will be listed in the refunds array of the order.

Webhook notification

When the status of a refund changes, Montonio sends a POST request to the notificationUrl you specified when creating the original Order. The request body contains a signed JWT in the refundToken field. For general information about webhooks, see Listen to webhooks.

{
"refundToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Refund Token contents

The refundToken is a JWT signed with your Secret Key using HS256. Decoding and verifying it gives you the following fields:

KeyTypeDescription
refundUuidstringThe UUID of the refund.
refundStatusstringThe current status of the refund. See Refund statuses below.
refundStatusDescriptionstring | nullAdditional detail about the status. Present when the refund is in PENDING (retrying) or REJECTED status. See Status descriptions below.
accessKeystringYour Access Key.
refundAmountnumberThe refund amount.
orderUuidstringThe UUID of the associated order.
iatnumberToken issued-at timestamp (seconds since Unix Epoch).
expnumberToken expiration timestamp. Refund tokens are valid for 7 days.

Example decoded token:

{
"refundUuid": "a721af46-2dcb-4223-a227-5f85d1606cfe",
"refundStatus": "SUCCESSFUL",
"refundStatusDescription": null,
"accessKey": "MY_ACCESS_KEY",
"refundAmount": 33.09,
"orderUuid": "4a9115b7-8e55-48f4-bd7e-febc2402e8a0",
"iat": 1692180523,
"exp": 1692785323
}

Verifying the Refund Token

Verify the refundToken the same way you verify an orderToken — using your Secret Key:

import jwt from 'jsonwebtoken';
const { refundToken } = req.body;
const decoded = jwt.verify(refundToken, 'MY_SECRET_KEY');
if (decoded.refundStatus === 'SUCCESSFUL') {
// Refund completed — update your system accordingly
} else if (decoded.refundStatus === 'REJECTED') {
// Refund failed — check refundStatusDescription for details
}

Refund statuses

StatusDescription
PENDINGThe refund has been created and is awaiting processing. If a previous attempt failed (e.g. due to insufficient funds), the system will retry automatically.
PROCESSINGThe refund is currently being processed.
SUCCESSFULThe refund has been completed successfully.
REJECTEDThe refund has permanently failed. Check refundStatusDescription for the reason.
CANCELEDThe refund was canceled.

Refund status descriptions

When a refund is in PENDING (retrying) or REJECTED status, the refundStatusDescription field provides additional detail:

DescriptionMeaning
INSUFFICIENT_FUNDSThe settlement account lacks sufficient funds to process the refund.
REFUND_EXCEEDS_ORDER_PAID_AMOUNTThe refund amount exceeds the paid amount of the order.
DECLINEDThe payment provider declined the refund.
EXPIRED_OR_CANCELLED_CARDThe customer’s card has expired or been cancelled.
LOST_OR_STOLEN_CARDThe customer’s card has been reported lost or stolen.
EXPIREDThe refund could not be processed after all retry attempts were exhausted.
OTHERThe refund failed for an unspecified reason.