Webhooks
Receive real-time event notifications via webhooks. Subscribe to order, payment, logistics, and merchant application events with automatic retries and signature verification.
Overview
Webhooks enable you to receive real-time notifications for payment-related events in Surfboard, eliminating the need for repeated polling of the Surfboard APIs. When an event occurs, Surfboard sends an HTTP POST request to a URL on your server with the event details in the request body. All webhook messages include a signature for authenticity verification.
Surfboard supports two webhook mechanisms:
- Console webhooks: Persistent, account-level subscriptions configured in the Surfboard Console. Support retries, failure alerts, and signature verification.
- Callback URL (per-order webhook): A dynamic webhook URL set per order via
controlFunctions.callBackUrl. Useful for order-level status updates during checkout.
Webhooks are also offered alongside other integration methods such as SSE (Server Sent Events) and event bus-based solutions (Kafka, Azure Event Stream, Google Pub/Sub, etc.).
Available Events
You can subscribe to the following event categories to receive real-time updates within your platform.
Order and Payment Events
Order and payment events provide real-time updates on order status and payment flow. These notifications help track orders, detect issues, and improve the checkout experience.
- Order Updated — The order has been modified (e.g. order lines changed).
- Order Payment Initiated — A payment attempt has started for the order.
- Order Payment Processed — The payment is being processed by the payment provider.
- Order Payment Completed — The payment has been successfully completed.
- Order Payment Failed — The payment attempt has failed.
- Order Payment Cancelled — The payment has been cancelled.
- Order Cancelled — The entire order has been cancelled.
- Order Terminal Event — A terminal-level event related to the order (e.g. device interaction).
Logistics Events
Logistics events notify you about updates on shipments, including terminals and accessories. These events help track order progress from placement to delivery.
- Logistics Order Update — A logistics shipment status has changed.
Merchant Application Events
Merchant application events provide updates during the onboarding process, from application creation to approval. These notifications help ensure smooth and timely onboarding for merchants.
- Application Initiated — A new merchant application has been created.
- Application Submitted — The application has been submitted for review.
- Application Signed — The application has been signed by the merchant.
- Application Started — Processing of the application has begun.
- Application Pending Merchant Information — Additional information is required from the merchant.
- Application Completed — The application review is complete.
- Application Merchant Created — The merchant account has been created.
- Application Expired — The application has expired.
- Application Rejected — The application has been rejected.
Getting Started
To set up webhooks via the Surfboard Console:
- Log in to the Surfboard Console.
- Click Add new Webhook.
- Enter a name and the URL of your webhook endpoint.
- Enter an email address to receive notifications in case of webhook failures.
- Choose which events you would like to receive.
- Save the webhook secret that is displayed. This secret is used to verify that messages originate from Surfboard. It is only shown once — store it securely.
- Click Test webhooks to send a test notification to your endpoint and confirm it is working.
Note: You can add multiple webhooks to listen to different events. You can also customise your URLs so that each endpoint receives only specific events — useful for microservice or service-oriented architectures.
Testing Webhooks
When you create or test a webhook in the Console, Surfboard sends a test message to verify your endpoint is reachable. The test message has the following structure:
{
"eventType": "test.webhook",
"metadata": {
"eventId": "string",
"created": 1234567890,
"retryAttempt": 0,
"webhookEventId": "string"
}
}
Your endpoint should return a 200 status code to acknowledge receipt.
Callback URL (Per-Order Webhook)
In addition to Console webhooks, you can set a per-order callback URL when creating an order. This is useful for receiving status updates for a specific order during checkout.
Set controlFunctions.callBackUrl in the Create Order API request:
POST /merchants/:merchantId/orders
{
"terminal$id": "YOUR_TERMINAL_ID",
"orderLines": [ ... ],
"controlFunctions": {
"callBackUrl": "https://your-server.com/webhooks/payments",
"initiatePaymentsOptions": {
"paymentMethod": "CARD"
}
}
}
Note: Retries and alert emails are not supported for callback URL webhooks. The validation process is the same as regular webhooks — you can obtain the webhook certificate for signature validation from the Surfboard Console Dashboard.
Handling Duplicate Deliveries
Info: Surfboard guarantees at-least-once delivery for webhook callbacks. Because the system operates in a distributed multi-cloud environment, your endpoint may receive duplicate notifications for the same event. Surfboard performs deduplication on its side, but you must also handle duplicates on yours.
Use the combination of orderId and paymentId as your idempotency key. When you receive a callback, update the payment status to the value in the payload rather than applying it as an incremental state change.
Important: Due to network conditions, callbacks may arrive out of order. Once a payment reaches a terminal state — PAYMENT_COMPLETED, PAYMENT_FAILED, or PAYMENT_CANCELLED — do not overwrite it with an earlier status update. Your implementation should treat these three statuses as final and ignore any subsequent callbacks that would move the payment to a non-terminal state.
Handling Failures and Retries
Retry Logic
When a webhook delivery fails (your endpoint does not return a 200 status code), Surfboard retries automatically:
- First retry: 5 seconds after the initial failure.
- Subsequent retries: Up to 3 additional retries, spaced 30 seconds apart (30s, 60s, 90s after the first retry).
Failure Alerts and Automatic Disabling
- An alert email is sent on the first delivery failure.
- If the endpoint continues to fail, subsequent alerts are sent every 24 hours for up to 7 days.
- After 7 days of continuous failure with no action taken, the webhook is automatically disabled.
- To re-enable a disabled webhook, fix the underlying issue and re-run Test Webhook in the Console.
Failures on Surfboard’s Side
Surfboard guarantees to deliver events at least once. If Surfboard experiences an outage, all queued events are republished once the servers recover. Ensure your system can handle a burst of incoming events in this scenario.
Tip: As a safety net for payment events, perform a status query via the API if you have not received a webhook within 60 seconds of initiating a payment. Do not rely solely on webhooks for critical payment status confirmation.
Verifying Webhook Signatures
Every webhook event is signed using the secret key provided when you created the webhook. The signature is included in the x-webhook-signature header of the POST request. Always validate this signature to confirm that the message originates from Surfboard.
The signature is an HMAC-SHA512 hash of the JSON request body, encoded as Base64. Below are examples in several languages:
TypeScript
import { createHmac } from 'node:crypto';
function generateHMACSignature(certificate: string, message: string): string {
return createHmac('sha512', certificate)
.update(message)
.digest()
.toString('base64');
}
// Verify incoming webhook
function verifyWebhook(secret: string, body: string, receivedSignature: string): boolean {
const expectedSignature = generateHMACSignature(secret, body);
return expectedSignature === receivedSignature;
}
PHP
<?php
function generateHMACSignature($certificate, $message) {
return base64_encode(hash_hmac('sha512', $message, $certificate, true));
}
// Verify incoming webhook
$certificate = 'YOUR_WEBHOOK_SECRET';
$body = file_get_contents('php://input');
$receivedSignature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];
$expectedSignature = generateHMACSignature($certificate, $body);
if ($expectedSignature === $receivedSignature) {
// Signature is valid
http_response_code(200);
} else {
// Signature mismatch -- reject the request
http_response_code(401);
}
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class WebhookVerifier {
public static String generateHMACSignature(String certificate, String message) {
try {
Mac hmac = Mac.getInstance("HmacSHA512");
SecretKeySpec secretKey = new SecretKeySpec(
certificate.getBytes(StandardCharsets.UTF_8), "HmacSHA512"
);
hmac.init(secretKey);
byte[] hash = hmac.doFinal(message.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("Failed to generate HMAC signature", e);
}
}
}
.NET
using System;
using System.Security.Cryptography;
using System.Text;
public static class WebhookVerifier
{
public static string GenerateHMACSignature(string certificate, string message)
{
using (HMACSHA512 hmac = new HMACSHA512(Encoding.UTF8.GetBytes(certificate)))
{
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToBase64String(hash);
}
}
}
Python
import base64
import hashlib
import hmac
def generate_hmac_signature(certificate, message):
signature = hmac.new(certificate.encode(), message.encode(), hashlib.sha512)
return base64.b64encode(signature.digest()).decode()
Go
package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/base64"
)
func generateHMACSignature(certificate, message string) string {
key := []byte(certificate)
h := hmac.New(sha512.New, key)
h.Write([]byte(message))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
Best Practices
- Return 200 quickly. Process webhook payloads asynchronously. Return a
200response before performing business logic to avoid timeouts and retries. - Always verify signatures. Validate the
x-webhook-signatureheader on every incoming webhook to confirm it originates from Surfboard. - Handle duplicates idempotently. Use
orderId+paymentIdas your idempotency key and treat terminal payment statuses as final. - Plan for retries. Your endpoint may receive the same event multiple times. Ensure your processing logic is idempotent.
- Query on timeout. If you have not received a payment event within 60 seconds, query the order status via the API as a fallback.
- Use HTTPS. Always use HTTPS URLs for webhook endpoints to protect data in transit.
- Monitor your endpoints. Watch for failure alert emails and resolve issues promptly to avoid your webhook being disabled after 7 days.
Related Guides
- Notification Subscriptions — Set up persistent email, Slack, and SFTP delivery for settlement reports and operational alerts.
API Reference
| Action | Method | Endpoint |
|---|---|---|
| Set per-order webhook | POST | /merchants/:merchantId/orders (via controlFunctions.callBackUrl) |
Other Guides
Tap to Pay on iPhone SDK
Accept contactless payments directly on iPhone. Complete integration guide for Surfboard's iOS SoftPOS SDK -- from setup to production.
Android SoftPOS SDK
Turn Android devices into payment terminals with the Surfboard Android SoftPOS SDK. Complete integration guide from setup to production.
EMV Terminal Integration
Integrate traditional card-present terminals through Surfboard's unified API. From account setup to live payments in one guide.
Payment Page
Redirect customers to a Surfboard-hosted checkout page. The fastest way to accept online payments with minimal integration effort.
Inter-App Integration
Integrate your POS app with CheckoutX using native app switch. Register terminals, process payments, and scan NFC tags through a bi-directional deep link flow.
Self-Hosted Checkout
Embed a payment form directly in your web app with the Surfboard Online SDK. Full UI control with Surfboard handling PCI compliance.
Server-to-Server API
Process online payments entirely from your backend with Merchant Initiated Transactions. Full control over recurring payments, subscriptions, and tokenized card flows.
Create an Order
Learn how to create orders with line items, tax, customer details, and control functions. The starting point for accepting payments with the Surfboard API.
Merchant Onboarding
Set up merchants and stores on the Surfboard platform. Walk through the full onboarding flow from merchant creation to KYB completion and store setup.
Payment Lifecycle
Manage the full payment lifecycle from order creation through capture, void, cancel, and refund operations using the Surfboard Payments API.
Capture a Payment
Finalize a previously authorized payment by capturing funds. Covers delay capture and pre-authorization flows with step-by-step API examples.
Terminal & Device Management
Manage payment terminals and devices via the Surfboard API. Register in-store and online terminals, configure settings, and handle device operations.
Cancel a Payment
Stop an in-progress payment before it completes. Use cancellation when a customer abandons checkout or a payment needs to be halted mid-process.
Notification Subscriptions
Subscribe to persistent, account-level event notifications delivered via email, Slack, or SFTP. Receive settlement reports and operational alerts for merchants and partners.
Recurring Payments
Implement subscription billing and recurring charges using tokenization, recurring payment configuration, and Merchant Initiated Transactions.
Void a Payment
Reverse a completed payment before settlement. Voiding stops funds from transferring to the merchant's account, avoiding incorrect transactions.
Receipts
Generate, email, print, and customise receipts for in-store transactions using the Surfboard Receipts API.
Refund an Order
Process a full refund by creating a return order with negative quantities. Covers the complete refund flow with API examples and payment method requirements.
Partial Refund
Refund specific items or a reduced amount from a completed order. Process partial returns by creating a return order with only the items to be refunded.
Tips Configuration
Configure tipping on Surfboard payment terminals at the merchant, store, or terminal level using a hierarchical override model.
NFC Tag Reading
Use the NFC Reading API to create tag-reading sessions on payment terminals, scan NFC/RFID-tagged products, and retrieve scanned tag data.
Partial Payments
Split an order across multiple payment methods or transactions. Accept card, cash, and Swish in any combination to settle a single order.
Multi-Merchant Terminals
Set up shared payment terminals for multiple merchants using the Multi-Merchant Group API. Ideal for food courts, events, and co-located businesses.
Store Management
Create, update, verify, and manage in-store and online stores using the Surfboard Payments Store APIs.
Gift Cards & Promotions
Issue and manage gift cards, track transactions, and create marketing promotions using the Surfboard Payments APIs.
Product Catalog
Create and manage product catalogs, products, variants, inventory levels, and analytics through the Catalog API.
Settlements & Reporting
Retrieve settlement reports, view adjustments, manage merchant charges, and register customer profiles for reconciliation and billing.
Account & Service Provider Management
Create merchant and partner accounts, manage user roles, register service providers, and configure external notifications via the Surfboard API.
Payment Methods
Activate, deactivate, and list payment methods for a merchant. Manage card, Swish, Klarna, AMEX, Vipps, MobilePay, and more via the API or Partner Portal.
Client Auth Tokens
Generate client-side authentication tokens for secure API access from browsers and mobile apps without exposing your API key or secret.
Partner Branding
Configure white-label branding for terminals and payment pages. Set colors, fonts, logos, and cover images at the partner level via API or Partner Portal.
Ready to get started?
Create a sandbox account and start building your integration today.