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.
Provide a webhook URL
Section titled “Provide a webhook URL”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.
Receive the webhook
Section titled “Receive the webhook”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.
Verify the webhook signature
Section titled “Verify the webhook signature”Every webhook request contains an ICEPAY-Signature header.
Before processing the webhook, verify this signature using your Merchant Secret.
ICEPAY generates the signature by:
- Calculating an HMAC using SHA-256, your Merchant Secret, and the webhook request body.
- 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.');}Node.js
Section titled “Node.js”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.');}Process the update
Section titled “Process the update”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:
-
Verify the signature
Reject the request if the
ICEPAY-Signaturecannot be verified. -
Find the payment
Look up the ICEPAY payment
keystored with your order when the payment was created. Reject unknown keys; do not attach an unfamiliar payment to an order using only itsreference. -
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. -
Check fulfilment requirements
Base your
financialStatuschecks onpaymentMethod.typein 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 untilfinancialStatusisclearedbefore 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
statusiscompletedwithout requiringfinancialStatusto becleared. -
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.
-
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:
- In the ICEPAY Portal, go to Merchants → Select your merchant → Settings → Redirect back to merchant.
- Select Redirect back to Merchant instead of Redirect back to ICEPAY Checkout Page.
- 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.
Handle duplicate webhook updates
Section titled “Handle duplicate webhook updates”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.
Handle delayed webhook updates
Section titled “Handle delayed webhook updates”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.
Respond to ICEPAY
Section titled “Respond to ICEPAY”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.
When the customer returns
Section titled “When the customer returns”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 the latest state of a payment directly from ICEPAY.

