Webhooks
Webhooks are automated notifications sent from Worldline Direct to your server, enabling you to receive messages about transaction results and status changes without continuous polling.
Benefits
- Automated Processing - Automate transaction workflows
- Real-time Updates - Track authorization results instantly
- Offline Events - Monitor captures, refunds after initial transactions
- No Polling - Eliminate regular status-checking API calls
Webhooks are asynchronous and not suitable for real-time checkout decisions. Use GetHostedCheckoutStatus or GetPaymentDetails for immediate status checks.
Configuration
Step 1: Generate Webhook Credentials
- Log into the Merchant Portal
- Navigate to Developer > Webhooks
- Click "Generate webhooks keys"
- Store the Secret Webhook Key immediately
The secret key displays for 60 seconds only. Store it securely. Regenerating keys revokes existing ones immediately.
Step 2: Define Webhook Endpoints
Option A: Merchant Portal (Hard-coded)
- Go to Developer > Webhooks
- Click "Add webhook endpoint"
- Add up to five URLs
Option B: Per-Request (Dynamic)
Include endpoints in your API request:
{
"order": { ... },
"feedbacks": {
"webhookUrls": [
"https://yoursite.com/webhooks/primary",
"https://yoursite.com/webhooks/backup"
]
}
}
Dynamic webhooks are recommended for multi-webshop environments where each shop needs different notification URLs.
Step 3: Build Webhook Handler
Your HTTPS endpoint must:
- Accept POST requests with JSON body
- Verify message signatures
- Respond with 2xx HTTP status code
- C#
- Java
- PHP
[HttpPost("webhook")]
public async Task<IActionResult> HandleWebhook()
{
// Read the raw request body
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
// Get signature headers
var keyId = Request.Headers["X-GCS-KeyId"].FirstOrDefault();
var signature = Request.Headers["X-GCS-Signature"].FirstOrDefault();
// Verify and parse the webhook
var webhookHelper = new WebhooksHelper(new InMemorySecretKeyStore());
var events = webhookHelper.Unmarshal(body, new List<RequestHeader>
{
new RequestHeader("X-GCS-KeyId", keyId),
new RequestHeader("X-GCS-Signature", signature)
});
foreach (var webhookEvent in events)
{
// Process the event
ProcessWebhookEvent(webhookEvent);
}
// Always return 2xx immediately
return Ok();
}
private void ProcessWebhookEvent(WebhooksEvent webhookEvent)
{
var paymentId = webhookEvent.Payment?.Id;
var eventType = webhookEvent.Type;
var statusCode = webhookEvent.Payment?.StatusOutput?.StatusCode;
// Update your database asynchronously
// Don't block the webhook response
}
@PostMapping("/webhook")
public ResponseEntity<Void> handleWebhook(
@RequestBody String body,
@RequestHeader("X-GCS-KeyId") String keyId,
@RequestHeader("X-GCS-Signature") String signature) {
List<RequestHeader> headers = Arrays.asList(
new RequestHeader("X-GCS-KeyId", keyId),
new RequestHeader("X-GCS-Signature", signature)
);
WebhooksHelper helper = new WebhooksHelper(new InMemorySecretKeyStore());
List<WebhooksEvent> events = helper.unmarshal(body, headers);
for (WebhooksEvent event : events) {
processWebhookEvent(event);
}
// Always return 2xx immediately
return ResponseEntity.ok().build();
}
private void processWebhookEvent(WebhooksEvent event) {
String paymentId = event.getPayment().getId();
String eventType = event.getType();
Integer statusCode = event.getPayment().getStatusOutput().getStatusCode();
// Process asynchronously
}
<?php
$body = file_get_contents('php://input');
$keyId = $_SERVER['HTTP_X_GCS_KEYID'];
$signature = $_SERVER['HTTP_X_GCS_SIGNATURE'];
$headers = [
new RequestHeader('X-GCS-KeyId', $keyId),
new RequestHeader('X-GCS-Signature', $signature)
];
$helper = new WebhooksHelper(new InMemorySecretKeyStore());
$events = $helper->unmarshal($body, $headers);
foreach ($events as $event) {
$paymentId = $event->getPayment()->getId();
$eventType = $event->getType();
$statusCode = $event->getPayment()->getStatusOutput()->getStatusCode();
// Process the event
}
// Return 200 OK
http_response_code(200);
Decouple your business logic from webhook handling. Return a 2xx status code immediately, then process the event asynchronously to prevent false delivery failures.
Webhook Events
Payment Events
| Event | Description | Status Code |
|---|---|---|
payment.created | Transaction created | 0 |
payment.redirected | Customer redirected to 3DS | 46 |
payment.authorization_requested | Auth request sent | 51 |
payment.pending_approval | Awaiting merchant approval | 56 |
payment.pending_capture | Authorization complete, awaiting capture | 5 |
payment.capture_requested | Capture queued | 91 |
payment.captured | Capture confirmed | 9 |
payment.rejected | Transaction declined | 2 |
payment.rejected_capture | Capture rejected | 93 |
payment.cancelled | Transaction cancelled | 1, 6 |
payment.refunded | Refund processed | 8 |
Refund Events
| Event | Description |
|---|---|
refund.refund_requested | Refund queued |
Payment Link Events
| Event | Description |
|---|---|
paymentlink.created | Payment link generated |
paymentlink.clicked | Payment link accessed |
paymentlink.paid | Successful payment via link |
paymentlink.cancelled | Payment link cancelled |
paymentlink.expired | Link expired |
Sample Webhook Messages
payment.created
{
"apiVersion": "v1",
"created": "2024-01-15T14:30:00.000+01:00",
"id": "34b8a607-1fce-4003-b3ae-a4d29e92b232",
"merchantId": "YOUR_MERCHANT_ID",
"payment": {
"paymentOutput": {
"amountOfMoney": {
"amount": 2980,
"currencyCode": "EUR"
},
"references": {
"merchantReference": "order-12345"
},
"cardPaymentMethodSpecificOutput": {
"paymentProductId": 1,
"card": {
"cardNumber": "************1111",
"expiryDate": "1225"
}
},
"paymentMethod": "card"
},
"status": "CREATED",
"statusOutput": {
"statusCode": 0,
"statusCategory": "CREATED"
},
"id": "000000123400000012340000100001"
},
"type": "payment.created"
}
payment.captured
{
"apiVersion": "v1",
"created": "2024-01-15T14:35:00.000+01:00",
"id": "7aeb0c3d-066e-4d31-bfe9-f9b5e48414df",
"merchantId": "YOUR_MERCHANT_ID",
"payment": {
"status": "CAPTURED",
"statusOutput": {
"statusCode": 9,
"statusCategory": "COMPLETED"
},
"id": "000000123400000012340000100002"
},
"type": "payment.captured"
}
Handling Duplicates
Duplicate webhook events are a feature of reliable delivery, not an error. Design your system to handle them:
public async Task ProcessWebhookEvent(WebhooksEvent webhookEvent)
{
var eventId = webhookEvent.Id;
var paymentId = webhookEvent.Payment?.Id;
var eventType = webhookEvent.Type;
// Check if already processed
if (await IsEventProcessed(eventId))
{
return; // Skip duplicate
}
// Mark as processed
await MarkEventProcessed(eventId);
// Process the event
await HandleEvent(paymentId, eventType);
}
Retry Strategy
If delivery fails, the platform retries five times:
| Attempt | Delay |
|---|---|
| Initial | Immediate |
| Retry 1 | 10 minutes |
| Retry 2 | 1 hour |
| Retry 3 | 2 hours |
| Retry 4 | 8 hours |
| Retry 5 | 24 hours |
Each retry includes a retry-count header (0 for initial attempt).
Testing Webhooks
Validate Credentials
curl -X POST https://payment.preprod.direct.worldline-solutions.com/v2/YOUR_MERCHANT_ID/webhooks/validateCredentials \
-H "Content-Type: application/json" \
-d '{
"key": "your-webhook-key-id",
"secret": "base64-encoded-hmac-secret"
}'
Send Test Message
curl -X POST https://payment.preprod.direct.worldline-solutions.com/v2/YOUR_MERCHANT_ID/webhooks/sendTestWebhook \
-H "Content-Type: application/json" \
-d '{
"url": "https://yoursite.com/webhooks"
}'
The platform sends a test webhook with type: "payment.test".
Payment ID Changes
The payment.id changes during maintenance operations:
| Operation | payment.id | statusCode |
|---|---|---|
| Authorization | payment.id1 | 5 |
| Capture | payment.id2 | 9 |
| Refund | payment.id3 | 8 |
Use GetPaymentDetails to track operations.id across the transaction lifecycle.
Best Practices
- Return 2xx Immediately - Don't block on business logic
- Handle Duplicates - Use event ID for idempotency
- Process Asynchronously - Queue events for background processing
- Implement Fallbacks - Use
GetPaymentDetailsas backup - Verify Signatures - Always validate webhook authenticity
- Log Everything - Keep detailed logs for debugging
- Monitor Failures - Alert on repeated delivery failures
Next Steps
- Set up Hosted Checkout with webhooks
- Implement Server-to-Server integration
- Learn about 3-D Secure authentication