Skip to content

Process webhooks

Receive and verify payment updates from ICEPAY.

ICEPAY sends a webhook when the state of a payment changes.

Use these notifications to keep the payment and corresponding order in your system synchronised with ICEPAY.

When creating a payment, provide a publicly accessible HTTPS endpoint in webhookUrl:

{
"webhookUrl": "https://merchant.example.com/payment-webhook"
}

ICEPAY sends payment updates to this URL using an HTTP POST request.

The webhook body contains the current payment information and uses the same structure as the response returned when you retrieve a payment.

For example, a completed card payment sends:

{
"key": "pi-01j1pta7ymwcjk25q4rtpqmn2q",
"status": "completed",
"financialStatus": "uncleared",
"amount": {
"value": 2999,
"currency": "eur"
},
"paymentMethod": {
"type": "card",
"paymentAccountReference": "WCJ205PL1SMPX85WGKNBY89HNZ38J"
},
"reference": "ORD-16307"
}

After verifying the signature, use the ICEPAY payment key you stored when creating the payment to find the corresponding record in your system. Use your reference for reconciliation; a reference alone does not establish that a payment belongs to an order.

The paymentMethod.paymentAccountReference field contains the Payment Account Reference (PAR).

For an explanation of payment states, see the Payment lifecycle.

Every webhook request contains an ICEPAY-Signature header.

Before processing the webhook, verify this signature using your Merchant Secret.

ICEPAY generates the signature by:

  1. Calculating an HMAC using SHA-256, your Merchant Secret, and the webhook request body.
  2. Base64 encoding the resulting HMAC.
$merchantSecret = getenv('ICEPAY_MERCHANT_SECRET');
if (!is_string($merchantSecret) || $merchantSecret === '') {
throw new RuntimeException('Missing ICEPAY Merchant Secret.');
}
$signature = $_SERVER['HTTP_ICEPAY_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
if ($body === false) {
throw new RuntimeException('Unable to read the webhook body.');
}
$calculatedSignature = base64_encode(
hash_hmac('sha256', $body, $merchantSecret, true)
);
if (!hash_equals($calculatedSignature, $signature)) {
throw new RuntimeException('Invalid ICEPAY webhook signature');
}
$payment = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($payment) || array_is_list($payment)) {
throw new RuntimeException('Expected a payment object.');
}
import { createHmac, timingSafeEqual } from 'node:crypto';
const merchantSecret = process.env.ICEPAY_MERCHANT_SECRET;
if (!merchantSecret) {
throw new Error('Missing ICEPAY Merchant Secret.');
}
const signature = request.headers.get('ICEPAY-Signature');
// Use the original bytes before decoding or parsing the body.
const body = Buffer.from(await request.arrayBuffer());
const calculatedSignature = createHmac('sha256', merchantSecret)
.update(body)
.digest('base64');
const expected = Buffer.from(calculatedSignature, 'utf8');
const received = Buffer.from(signature ?? '', 'utf8');
if (
received.length !== expected.length ||
!timingSafeEqual(received, expected)
) {
throw new Error('Invalid ICEPAY webhook signature');
}
const payment = JSON.parse(
new TextDecoder('utf-8', { fatal: true }).decode(body),
);
if (!payment || typeof payment !== 'object' || Array.isArray(payment)) {
throw new Error('Expected a payment object.');
}

A valid signature authenticates the notification. Before updating an order, also check that the notification matches the payment you created and the expected merchant and environment.

A typical webhook handler should:

  1. Verify the signature

    Reject the request if the ICEPAY-Signature cannot be verified.

  2. Find the payment

    Look up the ICEPAY payment key stored with your order when the payment was created. Reject unknown keys; do not attach an unfamiliar payment to an order using only its reference.

  3. Validate the payment details

    Before fulfilment, match the payment’s merchant, processing mode (isTest), amount, and currency against your server-stored order and merchant configuration. A test payment must never fulfil a live order. Reject mismatches without changing the order.

  4. Check fulfilment requirements

    Base your financialStatus checks on paymentMethod.type in the verified webhook, rather than the payment method originally selected in your checkout. Apply the settlement requirements for the method actually used. For Pay by Bank, EPS, and Online Überweisen, wait until financialStatus is cleared before fulfilling the order if you require confirmation that the funds have arrived.

    For methods that guarantee settlement, such as cards, Bancontact, and iDEAL | Wero, you can fulfil once status is completed without requiring financialStatus to be cleared.

  5. Apply the update atomically

    Check for duplicates and stale state, then record the accepted update and any fulfilment work in one database transaction. Serialise concurrent updates for the same payment.

  6. Respond with HTTP 200

    Return a successful response only after processing the verified update or durably queuing it. An already accepted duplicate can also receive HTTP 200.

Payment-method changes and redirect settings

Section titled “Payment-method changes and redirect settings”

Customers can change payment methods on ICEPAY Checkout. The settlement requirements can change with the payment method used.

The table below uses card as an example of a method with guaranteed settlement and Pay by Bank as an example of a method where authorisation does not guarantee receipt of funds. Bancontact and iDEAL | Wero follow the same settlement principle as card; EPS and Online Überweisen follow the same principle as Pay by Bank.

Example of a change on ICEPAY Checkout Fulfilment requirements
Card → Pay by Bank Apply Pay by Bank’s settlement requirements. Successful authorisation does not guarantee that the funds have arrived. If your business cannot accept the risk of the transfer not arriving, wait until financialStatus is cleared before fulfilling.
Pay by Bank → Card Once the card payment’s status is completed, you can fulfil without requiring financialStatus to be cleared, because card settlement is guaranteed. This allows you to ship sooner.

If the customer changes payment methods, use the settlement requirements of the method they actually paid with to decide when to fulfil the order.

Redirect settings for direct payment flows

Section titled “Redirect settings for direct payment flows”

If you redirect customers using the direct payment URL (links.direct), you can configure ICEPAY to return them to your application instead of the ICEPAY Checkout Page after the payment flow:

  1. In the ICEPAY Portal, go to Merchants → Select your merchant → Settings → Redirect back to merchant.
  2. Select Redirect back to Merchant instead of Redirect back to ICEPAY Checkout Page.
  3. Click Submit to save the setting.

Continue checking the payment state through verified webhooks or a server-side retrieval. The customer’s return to your application does not confirm that the payment succeeded or that the funds have arrived.

Your endpoint may receive the same payment update more than once.

Make sure processing the same update again does not repeat actions such as:

  • fulfilling an order;
  • sending a confirmation email;
  • updating inventory;
  • starting another action that should only happen once.

Store which payment states and actions you have already processed. When updating an order, use a database transaction or equivalent concurrency protection so that two webhook requests arriving at the same time cannot perform the same action twice.

If additional work is processed asynchronously, make sure those background jobs are also safe to run more than once.

Treat refunds and forwarded payments separately. They can change without the main payment status changing.

Webhook updates may arrive late or in a different order than expected.

Do not allow an older update to overwrite a newer payment state. If you cannot reliably determine which update is newer, retrieve the current payment through the ICEPAY Checkout API before updating your order.

Keep the payment state and completed actions in durable storage so delayed or repeated webhook updates cannot cause an order to be fulfilled twice.

Return an HTTP 200 response after processing the verified webhook successfully or durably queuing the verified update.

If ICEPAY does not receive a successful response, it may retry delivering the webhook.

Your endpoint should complete webhook processing quickly. If persistence or queuing fails, return an unsuccessful response so the update can be retried.

The customer returning to your redirectUrl and the webhook reaching your backend are separate events.

When the customer returns, your system may already have processed the webhook. If the latest payment state is not yet available in your system, retrieve the payment once from ICEPAY to confirm its current state.


Retrieve a payment

Retrieve the latest state of a payment directly from ICEPAY.