Skip to content

Checkout API

Authentication

To authenticate requests to the Checkout API we use Basic authentication
Your Merchant ID is used as the username, your Merchant Secret is used as the password

Both of these values can be obtained from the ICEPAY Portal
They are then added into an Authorization header using the format Basic <base64 username:password\>

Example

Merchant ID is 10000
Merchant Secret is xxxxxx
The base64 encoding of the password and username, 10000:xxxxxx, will result in MTAwMDA6eHh4eHh4

Authentication is required for all request.

An ICEPAY Merchant can be set to test or live processing mode via the ICEPAY Portal
For an explanation, please see our Testing page

Creating a checkout

When creating a checkout no payment method has to be provided.
The customer will be able to choose this on the ICEPAY Checkout Page

POST: https://checkout.icepay.com/api/payments

{
    "reference": "OR-123412", // Required string, max length of 255 characters
    "description": "Ice cream with sprinkles", // optional string,  max length of 255 characters, visible in the checkout. If absent the reference is used as the description.
    "amount": {
        "value": 299, // Required integer, in minor units and above zero
        "currency": "eur" // Required enum
    },
    "redirectUrl": "https://<redirectUrl>", // optional string, max length of 512 characters and https. This url is used to sent the customer back when the payment is completed or stopped
    "webhookUrl": "https://<postbackUrl>" // optional string, maximum length of 512 and https.
}

Use the reference value in the redirectUrl & webhookUrl

The ICEPAY Platform holds the customer for 5 seconds to ensure that the webhook to update your integration is processed.
You can identify a payer returning to your environment via the redirect based on the Checkout Key.
However, our best practice is to also use the value you use in the reference in the URL.

curl --silent --location --request POST 'https://checkout.icepay.com/api/payments' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic MTAwMDA6eHh4eHh4' \
--data '{
    "reference": "ORD-16307",
    "description": "Icecream dipped in Sprinkels",
    "amount": {
        "value": 299,
        "currency": "eur"
    },
    "redirectUrl": "https://<redirectUrl>",
    "webhookUrl": "https://<postbackUrl>"
}'
$merchantId = '10000';
$merchantSecret = 'xxxxxx';
$body = [
    'reference' => 'ORD-16307',
    'description' => 'Ice cream dipped in Sprinkles',
    'amount' => [
        'value' => 299,
        'currency' => 'eur'
    ],
    'webhookUrl' => 'https://<redirectUrl>',
    'redirectUrl' => 'https://<postbackUrl>',
];

$response = Http::withBasicAuth($merchantId, $merchantSecret)
    ->post('https://checkout.icepay.com/api/payments', $body);

Log::debug($response->json());
const merchantId = '10000';
const merchantSecret = 'xxxxxx';

const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Basic ${btoa(`${merchantId}:${merchantSecret}`)}`
};

const body = JSON.stringify({
    "reference": "ORD-16307",
    "description": "Icecream dipped in Sprinkels",
    "amount": {
        "value": 299,
        "currency": "eur"
    },
    "webhookUrl": "https://<postbackUrl>",
    "redirectUrl": "https://<redirectUrl>",
});

const result = await fetch('https://checkout.icepay.com/api/payments', {
    method: 'POST',
    headers: headers,
    body: body,
});

console.log(await result.json());

Example response when a payment has been successfully created:

    {
        "key": "pi-01j1ps8zf4jgnk0c3dnd477sp1",
        "status": "started",
        "amount": {
            "value": 299,
            "currency": "eur"
        },
        "paymentMethod": null,
        "description": "Ice cream dipped in Sprinkles",
        "reference": "ORD-16307",
        "webhookUrl": "https://<redirectUrl>",
        "redirectUrl": "https://<postbackUrl>",
        "merchant": {
            "id": 10000,
            "name": "IJs winkel"
        },
        "isTest": false,
        "refunds": [],
        "createdAt": "2024-07-01T09:16:06.500000Z",
        "expiresAt": "2024-07-01T13:16:06.433802Z",
        "updatedAt": "2024-07-01T09:16:06.500000Z",
        "meta": null,
        "links": {
            "checkout": "https://checkout.icepay.com/checkout/pi-01j1ps8zf4jgnk0c3dnd477sp1",
            "documentation": "https://docs.icepay.com"
        }
    }

The customer can be redirected to the checkout url in the links object to complete the payment. The key provided in the response is the unique identifier of the payment. This key is also used to create a refund or to retrieve the payment.

Extend payment

By default, the payment will stay open for 4 hours. By providing the expireAfter property this be extended up to 31 days.
The expireAfter property will take the amount of time it will stay open in minutes as an integer between 0 and 44640. This can be useful when, for example, printing the Checkout URL as QR code on an invoice.

Providing a payment method

When the customer chooses a payment method in your checkout the request body can be modified to include a paymentMethod object.

POST: https://checkout.icepay.com/api/payments

{
    "reference": "ORD-16307", // Required string, max length of 255 characters
    "description": "Ice cream dipped in Sprinkles", // optional string,  max length of 255 characters, visible in the checkout. If absent the reference is used a the description.
    "amount": {
        "value": 299, // Required integer, in minor units and above zero
        "currency": "eur" // Required enum
    },
    "paymentMethod": {
        "type": "card" // Optional enum: ideal, card, bancontact, paypal etc.
    },
    "redirectUrl": "https://<redirectUrl>", // optional string, max length of 512 characters and https. This url is used to sent the customer back when the payment is completed or stopped
    "webhookUrl": "https://<postbackUrl>" // optional string, maximale lengte 512 en https.
}

Payment Method Enum
Bancontact bancontact
iDEAL ideal
Online Überweisen onlineueberweisen
Visa & Mastercard card
PayPal paypal
EPS EPS

When redirecting the customer use the direct url from the links object instead of the checkout url to skip the ICEPAY Checkout Page.

Example response when a payment has been successfully created with a provided payment method:

{
    "key": "pi-01j1pta7ymwcjk25q4rtpqmn2q",
    "status": "started",
    "amount": {
        "value": 299,
        "currency": "eur"
    },
    "paymentMethod": null,
    "description": "Ice cream dipped in Sprinkles",
    "reference": "ORD-16307",
    "webhookUrl": "https://<redirectUrl>",
    "redirectUrl": "https://<postbackUrl>",
    "merchant": {
        "id": 10000,
        "name": "IJs winkel"
    },
    "isTest": false,
    "refunds": [],
    "createdAt": "2024-07-01T09:34:16.532000Z",
    "expiresAt": "2024-07-01T13:34:16.470510Z",
    "updatedAt": "2024-07-01T09:34:16.532000Z",
    "meta": null,
    "links": {
        "direct": "https://card.icepay.com/card/<key>",
        "checkout": "https://checkout.icepay.com/checkout/pi-01j1pta7ymwcjk25q4rtpqmn2q",
        "documentation": "https://docs.icepay.com"
    }
}

Obtaining payment methods

GET: https://checkout.icepay.com/api/payments/methods

curl --silent --location --request GET 'https://checkout.icepay.com/api/payments/methods' \
--header 'Authorization: Basic MTAwMDA6eHh4eHh4'
$merchantId = '10000';
$merchantSecret = 'xxxxxx';

$response = Http::withBasicAuth($merchantId, $merchantSecret)
    ->get('https://checkout.icepay.com/api/payments/methods');

Log::debug($response->json());
const merchantId = '10000';
const merchantSecret = 'xxxxxx';

const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Basic ${btoa(`${merchantId}:${merchantSecret}`)}`
};

const result = await fetch('https://checkout.icepay.com/api/payments/methods', {
    method: 'GET',
    headers: headers,
});

console.log(await result.json());

The response wil be a list of objects with IDs and descriptions for the payment methods. The ID can be used as an id on the type in the paymentMethod object when creating a direct payment.

Example get payment methods call:

[
    {
        "id": "ideal",
        "description": "iDEAL"
    },
    {
        "id": "paypal",
        "description": "PayPal"
    },
    {
        "id": "card",
        "description": "Card"
    },
    {
        "id": "bancontact",
        "description": "Bancontact"
    }
]

Status

When the status of a payment changes we send a request to the provided webhook to inform you about the change.

flowchart LR
S[started] --> C[completed]
S[started] --> P[pending]
S[started] --> E[expired]
S[started] --> CA[cancelled]

P --> C
P --> S

E --> C
E --> P

The initial status of a payment is started. When cancelling a payment, no postback is provided.

When the customer pays the status goes to completed. For some payment methods it can take a little while before we receive confirmation that the payment has been successful. In this case we set the status to pending. If the payment was not successfully we change the status back to started, if it is successful we change the status to completed.

If the payment has not been completed within its timeframe, the status will change to expired.

Generally speaking an expired payment will not change status. However, if a customer is, for example, on the iDEAL payment page and completes the payment we will change the status to completed.

Payment flow

sequenceDiagram
    participant App as Webshop
    participant Api as Checkout API
    participant Page as Payment Page
    App->>Api: Create a payment
    Api->>App: Returns the urls for the next step
    App->>Page: Redirect the customer to the payment page (or checkout)
    Note over Page: Customer Completes payment
    Page->>Api: Customer redirects back to ICEPAY
    loop Until a 200 response <br> or multiple attempts spread over 24h
        Api-->>App: We send a postback if a webhookUrl is provided
    end
    Api->>App: Customer is send to the redirectUrl
    Note over App: Once the customer returns check <br> for the status of the payment. If the payment has been marked <br> completed in your system the postback has been resolved, <br> otherwise retrieve the status from the API to verify the status of the payment

Retrieving a payment

GET: https://checkout.icepay.com/api/payments/{key}

curl --silent --location --request GET 'https://checkout.icepay.com/api/payments/{key}' \
--header 'Authorization:  Basic MTAwMDA6eHh4eHh4'
$merchantId = '10000';
$merchantSecret = 'xxxxxx';

$response = Http::withBasicAuth($merchantId, $merchantSecret)->get('https://checkout.icepay.com/api/payments/{key}');

Log::debug($response->json());
const merchantId = '10000';
const merchantSecret = 'xxxxxx';

const headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Basic ' + btoa(`${merchantId}:${merchantSecret}`)
};

const result = await fetch('https://checkout.icepay.com/api/payments/{key}', {
    method: 'GET',
    headers: headers,
});

console.log(await result.json());

The following is an example response of a payment:

{
    "key": "pi-01j1pta7ymwcjk25q4rtpqmn2q",
    "status": "started",
    "amount": {
        "value": 299,
        "currency": "eur"
    },
    "paymentMethod": null,
    "description": "Ice cream dipped in Sprinkles",
    "reference": "ORD-16307",
    "webhookUrl": "https://<redirectUrl>",
    "redirectUrl": "https://<postbackUrl>",
    "merchant": {
        "id": 10000,
        "name": "IJs winkel"
    },
    "isTest": false,
    "refunds": [],
    "createdAt": "2024-07-01T09:34:16.533000Z",
    "expiresAt": "2024-07-01T13:34:16.470000Z",
    "updatedAt": "2024-07-01T09:34:16.533000Z",
    "meta": null,
    "links": {
        "checkout": "https://checkout.icepay.com/checkout/pi-01j1pta7ymwcjk25q4rtpqmn2q",
        "documentation": "https://docs.icepay.com"
    }
}

Resolving the webhook

When a payment changes status, a request is sent to the provided webhook url. The data received from the webhook is structured the same as the response from a GET request. The request has an ICEPAY-Signature header. This header contains a base64 encode sha256 hmac with the merchant secret as the secret and the body json encoded as the data. Please note that it can, but is not guaranteed to, contain escapes slashes (\/).

$merchantSecret = 'xxxxxx';
$signature = $request->header('ICEPAY-Signature');

$calculatedSignature = base64_encode(hash_hmac('sha256', json_encode($body), $merchantSecret, true));

Log::debug( $signature === $calculatedSignature);
import {createHmac} from 'crypto';

// ...

const merchantSecret = 'xxxxxx';
const signature = ''; // get the request header in utf8

// our signature does contain \/ if hardcoding the body during testing use the String.raw`` template literal
const body = ''; // get the request body in utf8

const calculatedSignature = createHmac('sha256', merchantSecret)
    .update(body)
    .digest('base64');

console.log(signature === calculatedSignature);

Example webhook body:

{
    "key": "pi-01j1pta7ymwcjk25q4rtpqmn2q",
    "status": "started",
    "amount": {
        "value": 299,
        "currency": "eur"
    },
    "paymentMethod": null,
    "description": "Ice cream dipped in Sprinkles",
    "reference": "ORD-16307",
    "webhookUrl": "https://<redirectUrl>",
    "redirectUrl": "https://<postbackUrl>",
    "merchant": {
        "id": 10000,
        "name": "IJs winkel"
    },
    "isTest": false,
    "refunds": [],
    "createdAt": "2024-07-01T09:34:16.533000Z",
    "expiresAt": "2024-07-01T13:34:16.470000Z",
    "updatedAt": "2024-07-01T09:34:16.533000Z",
    "meta": null,
    "links": {
        "checkout": "https://checkout.icepay.com/checkout/pi-01j1pta7ymwcjk25q4rtpqmn2q",
        "documentation": "https://docs.icepay.com"
    }
}

If no payment method was provided when creating the checkout, the postback will also show the payment method used to complete the payment.

Contact us if you want to whitelist the IPs of our servers.

Refunding a payment

POST https://checkout.icepay.com/api/payments/{id}/refund

{
    "reference": "RFD-00069", // Required string, max length of 255 characters
    "description": "Received choco dip instead of sprinkles", // Optional string, max length of 255 characters
    "amount": {
        "value": 299, // Required integer, in minor units and above zero
        // "currency" is not needed, we use the same as the original payment
    }
}

curl --silent --location --request POST 'https://checkout.icepay.com/api/payments/{key}/refund' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic MTAwMDA6eHh4eHh4' \
--data '{
    "reference": "RFD-00069",
    "description": "Received choco dip instead of sprinkles",
    "amount": {
        "value": 299
    }
}'
$merchantId = '10000';
$merchantSecret = 'xxxxxx';
$intentKey = 'pi-01j1pta7ymwcjk25q4rtpqmn2q';

$body = [
    'reference' => 'RFD-00069',
    'description' => 'Received choco dip instead of sprinkles',
    'amount' => [
        'value' => 299,
    ],
];

$response = Http::withBasicAuth($merchantId, $merchantSecret)
    ->post('https://checkout.icepay.com/api/payments/' . $intentKey . '/refund', $body);

Log::debug($response->json());
const merchantId = '10000';
const merchantSecret = 'xxxxxx';
const intentKey = 'pi-01j1pta7ymwcjk25q4rtpqmn2q';

const headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Basic ' + btoa(`${merchantId}:${merchantSecret}`)
};

const body = JSON.stringify({
    'reference': 'RFD-00069',
    'description': 'Received choco dip instead of sprinkles',
    'amount': {
        'value': 299,
    }
});

const result = await fetch(`https://checkout.icepay.com/api/payments/${intentKey}/refund`, {
    method: 'POST',
    headers: headers,
    body: body,
});

console.log(await result.json());

After creating a refund the following response can be expected:

{
    "key": "pr-01j1sr38kspvq2z5xek20y565w",
    "status": "completed",
    "amount": {
        "value": 299,
        "currency": "eur"
    },
    "description": "Received choco dip instead of sprinkles",
    "reference": "RFD-00069",
    "payment": {
        "key": "pi-01j1sr24qph973tf3hgsvqe7h7",
        "status": "completed",
        "amount": {
            "value": 299,
            "currency": "eur"
        },
        "paymentMethod": {
            "type": "ideal"
        },
        "description": "Ice cream dipped in Sprinkles",
        "reference": "ORD-16307",
        "redirectUrl": "https://<redirect>",
        "webhookUrl": "https://<postback>",
        "merchant": {
            "id": 10000,
            "name": "IJsje van ICEPAY"
        },
        "isTest": true,
        "createdAt": "2024-07-02T12:52:37.000000Z",
        "expiresAt": "2024-07-02 16:52:37",
        "updatedAt": "2024-07-02T12:53:07.000000Z",
        "meta": null,
        "links": {
            "checkout": "https://checkout.icepay.com/checkout/pi-01j1sr38kspvq2z5xek20y565w",
            "documentation": "https://docs.icepay.com"
        }
    },
    "createdAt": "2024-07-02T12:53:13.000000Z",
    "updatedAt": "2024-07-02T12:53:13.000000Z"
}
Just like when creating a payment, its success can be determined based on the HTTP status.

Code Status
200-299 Successful
400-499 Error in the request
500-599 Contact ICEPAY

Most of the time the status of the refund wil go to completed. If the payment method requires some time to accept refund set the status to pending. If the refund was unsuccessful or declined the status wil be set to failed.

If the refund has a status of pending we will send an update via the webhook url. In the body will be a refunds property with the current status of the refunds.

Example refund postback:

{
    "id": "pi-01j1pta7ymwcjk25q4rtpqmn2q",
    "status": "completed",
    "amount": {
        "value": 299,
        "currency": "eur"
    },
    "paymentMethod": {
        "type": "ideal"
    },
    "description": "Ice cream dipped in Sprinkles",
    "reference": "ORD-16307",
    "redirectUrl": "https://<redirect>",
    "webhookUrl": "https://<postback>",
    "merchant": {
        "id": 10000,
        "name": "IJsje van ICEPAY"
    },
    "isTest": false,
    "createdAt": "2024-07-01T10:26:01.000000Z",
    "expiresAt": "2024-07-01T13:34:16.470000Z",
    "updatedAt": "2024-07-01T09:34:16.533000Z",
    "refunds": [
        {
            "id": "pr-01j1sr38kspvq2z5xek20y565w",
            "status": "completed",
            "amount": {
                "value": 299,
                "currency": "eur"
            },
            "description": "Received choco dip instead of sprinkles",
            "reference": "RFD-00069",
            "expiresAt": "2024-07-01T13:34:16.470000Z",
            "updatedAt": "2024-07-01T09:34:16.533000Z"
        }
    ],
    "links": {
        "checkout": "https://checkout.icepay.com/checkout/pi-01j1sr38kspvq2z5xek20y565w",
        "documentation": "https://docs.icepay.com"
    }
}