Setting up Webhooks

Learn how to configure and use webhooks to receive real-time updates from Koard's payment platform.

Get started with Koard

Koard allows you to add URLs that receive POST requests when specific events occur in your payment system. Each endpoint can be configured to receive a specific set of events, enabling real-time integration with your applications.

What you learn

In this guide, you'll learn:

  • How to set up webhook endpoints in Koard
  • How to configure event subscriptions and filters
  • How to test and verify webhook functionality
  • How to monitor webhook delivery and troubleshoot issues
  • Best practices for webhook security and reliability

Before you begin

This guide covers webhook configuration and management in Koard. For a better understanding of available events, see our Available Events guide. If you're ready to start processing payments, see our Running Payments guide.

What are Webhooks?

Webhooks are HTTP callbacks that Koard sends to your application when specific events occur. This allows you to receive real-time notifications about payment events, transaction updates, and other important changes without constantly polling our APIs.

Key benefits:

  • Real-time Updates: Get instant notifications about payment events
  • Automated Processing: Trigger automated workflows based on events
  • Reduced Polling: Eliminate the need to constantly poll for updates
  • Better User Experience: Provide immediate feedback to users

Setting Up Webhook Endpoints

Access the Developer Portal

To set up a new webhook endpoint, navigate to one of the following URLs based on your environment:

Environment URL
UAT https://app.uat.koard.com/developer
Production https://app.koard.com/developer

webhooks-1
Koard Developer Portal - Webhook Configuration

Create a New Endpoint

  1. Navigate to Add Endpoint: Click "Add Endpoint" on the right side of the developer portal page
  2. Configure HTTPS Endpoint: Enter your HTTPS endpoint URL that will receive POST requests
  3. Select Events: Choose which events you want to subscribe to
  4. Save Configuration: Complete the setup process

webhooks-2
Adding a new webhook endpoint in the Koard Developer Portal

Important: Currently, webhooks can only be managed via the Portal, but API management is on the roadmap for creating and managing endpoints and event filters.

Webhook Endpoint Requirements

Your webhook endpoint must meet these requirements:

  • HTTPS Only: All webhook endpoints must use HTTPS
  • POST Method: Endpoints must accept POST requests
  • JSON Payload: Events are sent as JSON in the request body
  • Quick Response: Return a 2xx status code quickly (within 30 seconds)

Webhook Configuration

Event Selection

Choose which events you want to receive based on your integration needs. If you don't specify any event types, by default your endpoint will receive all events, regardless of type. This can be helpful for getting started and testing, but we recommend selecting specific events for production.

Event Category Description Common Events
Transaction Events Transaction lifecycle events transaction.created, transaction.authorized, transaction.captured, transaction.refunded, transaction.reversed, transaction.settled
Account Events Account management events account.created, account.updated, account.deleted, account.restored
Terminal Events Terminal configuration events terminal.created, terminal.updated, terminal.deleted
Location Events Location management events location.created, location.updated, location.deleted
Batch Events Batch processing and settlement batch.created, batch.completed
API Key Events API key management events apikey.created, apikey.revoked

For a complete list of available events with schemas and examples, see our Available Events guide.

Webhook Headers

Koard uses industry-standard webhook headers powered by Svix for maximum compatibility:

Content-Type: application/json
svix-id: msg_p5jXN8AQM9LWM0D4loKWxJek
svix-timestamp: 1614265330
svix-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
User-Agent: Svix-Webhooks/1.0

Header Descriptions:

  • svix-id: Unique message identifier for idempotency
  • svix-timestamp: Unix timestamp when the webhook was sent (for replay attack prevention)
  • svix-signature: HMAC signature for verifying webhook authenticity
  • Content-Type: Always application/json

Retry Policy

Koard implements a robust retry policy with exponential backoff for failed webhook deliveries:

Retry Attempt Delay Time from First Attempt
1st Immediate 0 seconds
2nd 5 seconds 5 seconds
3rd 5 minutes ~5 minutes
4th 30 minutes ~35 minutes
5th 2 hours ~2 hours 35 minutes
6th 5 hours ~7 hours 35 minutes
7th 10 hours ~17 hours 35 minutes
8th 10 hours ~27 hours 35 minutes

Key Points:

  • Response Timeout: 15 seconds per attempt
  • Success Criteria: 2xx status code (200-299) indicates success
  • Failure Criteria: Any other status code or timeout triggers a retry
  • Endpoint Disabling: After 5 days of consecutive failures, endpoints are automatically disabled
  • Manual Recovery: Use the dashboard to recover or resend failed messages

Note: When responding to webhooks, return a 2xx status code quickly. Process complex workflows asynchronously to avoid timeouts.

Testing Your Webhook

Create a Test Endpoint

To test your webhook submission, create a webhook that can accept all transaction events and use the Koard SDK to process some transactions:

// Express.js example webhook endpoint
const express = require('express');
const app = express();


app.use(express.json());




app.post('/webhooks/koard', (req, res) => {
const payload = req.body;
const headers = {
'svix-id': req.headers['svix-id'],
'svix-timestamp': req.headers['svix-timestamp'],
'svix-signature': req.headers['svix-signature']
};




try {
// Verify webhook signature using Svix library
const wh = new Webhook(process.env.KOARD_WEBHOOK_SECRET);
const verifiedPayload = wh.verify(JSON.stringify(payload), headers);




  
// Process webhook after verification
processWebhook(verifiedPayload);
res.status(200).send('OK');


  
    plaintext
    
    
  




} catch (err) {
console.error('Webhook verification failed:', err);
res.status(400).send('Invalid signature');
}
});




function processWebhook(payload) {
const { event, data } = payload;




// Handle different event types
switch (event) {
case 'transaction.created':
handleTransactionCreated(data);
break;
case 'transaction.captured':
handleTransactionCaptured(data);
break;
// Add more event handlers
default:
console.log('Unhandled event:', event);
}
}




app.listen(3000, () => {
console.log('Webhook endpoint listening on port 3000');
});

Test with Koard SDK

Use the Koard SDK to process transactions and trigger webhook events:

// iOS SDK example
import KoardMerchantSDK


// Process a test transaction
let paymentRequest = PaymentRequest(
amount: 1000, // $10.00
currency: .USD,
description: "Test webhook transaction"
)




KoardMerchantSDK.shared.processPayment(paymentRequest) { result in
switch result {
case .success(let response):
print("Payment successful: (response.transactionId)")
// This will trigger webhook events
case .failure(let error):
print("Payment failed: (error)")
}
}

webhooks-4
Testing webhook functionality with Koard SDK transactions

Webhook Security

Signature Verification

Why Verify Webhooks?

Webhook signatures let you verify that webhook messages are actually sent by Koard and not a malicious actor. This prevents:

  • Spoofing Attacks: Malicious actors sending fake webhooks
  • Replay Attacks: Old webhooks being resent
  • Man-in-the-Middle Attacks: Webhooks being intercepted and modified

For a detailed explanation, see why you should verify webhooks.

Koard uses Svix for webhook delivery, which provides official libraries for easy verification:

Node.js:

const { Webhook } = require("svix");


const secret = process.env.KOARD_WEBHOOK_SECRET; // Get from Koard dashboard




app.post('/webhooks/koard', (req, res) => {
const payload = JSON.stringify(req.body);
const headers = {
'svix-id': req.headers['svix-id'],
'svix-timestamp': req.headers['svix-timestamp'],
'svix-signature': req.headers['svix-signature']
};




try {
const wh = new Webhook(secret);
const verifiedPayload = wh.verify(payload, headers);




  
// Webhook is verified - process it
processWebhook(verifiedPayload);
res.status(200).json({ success: true });


  
    plaintext
    
    
  




} catch (err) {
console.error('Webhook verification failed:', err.message);
res.status(400).json({ error: 'Invalid signature' });
}
});

Python:

from svix.webhooks import Webhook
import os


webhook_secret = os.environ['KOARD_WEBHOOK_SECRET']




@app.route('/webhooks/koard', methods=['POST'])
def webhook_handler():
payload = request.get_data()
headers = {
'svix-id': request.headers.get('svix-id'),
'svix-timestamp': request.headers.get('svix-timestamp'),
'svix-signature': request.headers.get('svix-signature')
}




  
try:
    wh = Webhook(webhook_secret)
    msg = wh.verify(payload, headers)
    
    # Webhook is verified - process it
    process_webhook(msg)
    return jsonify({'success': True}), 200
except Exception as e:
    print(f'Webhook verification failed: {e}')
    return jsonify({'error': 'Invalid signature'}), 400


  
    plaintext
    
    
  


Go:

import (
    svix "github.com/svix/svix-webhooks/go"
)


func webhookHandler(w http.ResponseWriter, r *http.Request) {
webhookSecret := os.Getenv("KOARD_WEBHOOK_SECRET")




  
payload, _ := ioutil.ReadAll(r.Body)
headers := http.Header{}
headers.Set("svix-id", r.Header.Get("svix-id"))
headers.Set("svix-timestamp", r.Header.Get("svix-timestamp"))
headers.Set("svix-signature", r.Header.Get("svix-signature"))

wh, _ := svix.NewWebhook(webhookSecret)
err := wh.Verify(payload, headers)

if err != nil {
    w.WriteHeader(http.StatusBadRequest)
    return
}

// Webhook is verified - process it
processWebhook(payload)
w.WriteHeader(http.StatusOK)


  
    plaintext
    
    
  




}

For more examples in other languages (Ruby, PHP, Java, Rust, Kotlin, C#), see the Svix webhook verification documentation.

Manual Verification (Advanced)

If you prefer to verify signatures manually without using the Svix library:

const crypto = require('crypto');


function verifyWebhookSignature(payload, headers, secret) {
const timestamp = headers['svix-timestamp'];
const signature = headers['svix-signature'];
const msgId = headers['svix-id'];




// Check timestamp to prevent replay attacks (optional but recommended)
const timestampSeconds = parseInt(timestamp);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestampSeconds) > 300) { // 5 minute tolerance
throw new Error('Webhook timestamp too old');
}




// Create the signed content
const signedContent = ${msgId}.${timestamp}.${payload};




// Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedContent, 'utf8')
.digest('base64');




// Extract signature from header (format: "v1,signature")
const signatureParts = signature.split(',');
const headerSignature = signatureParts[1];




// Compare signatures
return crypto.timingSafeEqual(
Buffer.from(headerSignature),
Buffer.from(expectedSignature)
);
}

Important: Use the raw payload body for verification, not the parsed JSON. Different JSON parsers may produce different string representations.

HTTPS Requirements

  • HTTPS Only: Webhook endpoints must use HTTPS
  • Valid SSL Certificates: SSL certificates must be valid and trusted
  • No Self-Signed Certificates: Self-signed certificates are not allowed
  • TLS 1.2+: Minimum TLS version 1.2 required

Security Best Practices

  • Verify Signatures: Always verify webhook signatures
  • Use HTTPS: Only accept webhooks over HTTPS
  • Validate Payloads: Validate webhook payloads before processing
  • Rate Limiting: Implement rate limiting to prevent abuse
  • Logging: Log all webhook events for debugging and security

Monitoring and Logging

Webhook Logs

All webhooks come with detailed logging around deliverability, attempts, and activity history:

  1. Access Logs: Go to the Logs tab in the developer portal
  2. Filter Events: Filter by event type and message content
  3. View Details: Click on individual events to see delivery details
  4. Monitor Status: Track delivery status and retry attempts

webhooks-3
Webhook logs and delivery monitoring in the Koard Developer Portal

Delivery Status

Monitor webhook delivery status:

Status Description
Delivered Webhook successfully delivered and acknowledged
Pending Webhook delivery in progress or scheduled for retry
Failed Webhook delivery failed after all retry attempts

Troubleshooting

Common webhook issues and solutions:

Not Using the Raw Payload Body

This is the most common issue. When generating the signed content, Koard uses the raw string body of the message payload. If you convert JSON payloads into strings using methods like JSON.stringify(), different implementations may produce different string representations, leading to verification failures.

Solution: Use the raw request body exactly as received. In Express.js: use express.raw() or access req.body before JSON parsing.

Missing or Wrong Secret Key

Using an incorrect or outdated webhook secret will cause all verifications to fail.

Solution: Get your webhook secret from the Koard Developer Portal. Remember that secrets are unique to each endpoint.

Timestamp Too Old

Webhooks with timestamps older than 5 minutes are rejected to prevent replay attacks.

Solution: Ensure your server's system time is synchronized (use NTP).

Sending Wrong Response Codes

When Koard receives a 2xx status code (200-299), it's interpreted as successful delivery, even if your response payload indicates a failure.

Solution: Return appropriate status codes:

  • 200 for successful processing
  • 400-499 for client errors (will not retry)
  • 500-599 for server errors (will retry)

Response Timeouts

Webhooks that don't respond within 15 seconds are considered failed and will be retried.

Solution: Respond immediately with 200 OK and process webhooks asynchronously:

app.post('/webhooks/koard', async (req, res) => {
  // Verify signature
  const wh = new Webhook(secret);
  const payload = wh.verify(req.body, req.headers);


// Respond immediately
res.status(200).send('OK');




// Process asynchronously
queue.add('process-webhook', payload);
});

Failure Recovery

Re-enable a Disabled Endpoint

If all attempts to a specific endpoint fail for 5 days, the endpoint will be automatically disabled.

To re-enable:

  1. Go to the Koard Developer Portal
  2. Navigate to Webhooks
  3. Find the disabled endpoint
  4. Click "Enable Endpoint"

Recovering Failed Messages

Single Message Recovery:

  1. Find the message in the Developer Portal
  2. Click the options menu next to the attempt
  3. Click "Resend" to retry delivery

Bulk Message Recovery:

  1. Go to the endpoint details page
  2. Click "Options" → "Recover Failed Messages"
  3. Choose a time window to recover from
  4. All failed messages in that window will be resent

Recovery from Specific Timestamp:

  1. Find any message near your desired recovery point
  2. Click the options menu on that message
  3. Select "Replay all failed messages since this time"

Best Practices

Endpoint Design

  • Quick Response: Return 2xx status codes quickly (within 15 seconds)
  • Respond First, Process Later: Acknowledge receipt immediately, then process asynchronously
  • Disable CSRF Protection: Disable CSRF checks for webhook endpoints
  • Use Raw Body: Access raw request body for signature verification
  • Error Handling: Implement proper error handling and logging

Event Processing - Idempotency

Why Idempotency Matters:

Webhooks may be delivered more than once due to network issues, retries, or recovery operations. Your endpoint must handle duplicate events gracefully.

Implementation:

Use the svix-id header (message ID) to track processed events:

const processedEvents = new Set(); // In production, use a database


app.post('/webhooks/koard', (req, res) => {
const messageId = req.headers['svix-id'];




// Check if we've already processed this event
if (processedEvents.has(messageId)) {
console.log('Duplicate event ignored:', messageId);
return res.status(200).send('OK'); // Return success for duplicates
}




// Verify and process webhook
const wh = new Webhook(secret);
const payload = wh.verify(req.body, req.headers);




// Mark as processed BEFORE processing to prevent race conditions
processedEvents.add(messageId);




// Process the webhook
processWebhook(payload);




res.status(200).send('OK');
});

Best Practices:

  • Store message IDs in a database (Redis, PostgreSQL, etc.)
  • Set expiration on stored IDs (e.g., 7 days) to prevent infinite growth
  • Use database transactions to ensure idempotency
  • Return 200 OK for duplicate events (they're already processed)

Event Ordering

Important: Don't rely on webhook delivery order. Events may arrive out of sequence due to network conditions, retries, or processing delays.

Solution: Use timestamps from the event payload to determine the correct order:

function processWebhook(payload) {
  const { event, data } = payload;
  const eventTimestamp = data.created_at; // Unix timestamp


// Get the latest known state
const currentState = database.getTransaction(data.transaction_id);




// Only process if this event is newer
if (!currentState || eventTimestamp > currentState.last_updated) {
// Process the event
updateTransaction(data);
} else {
console.log('Ignoring out-of-order event');
}
}

Security

  • Always Verify Signatures: Never skip signature verification in production
  • Use Svix Libraries: Use official Svix libraries for proper verification
  • HTTPS Only: Use HTTPS for all webhook endpoints (required by Koard)
  • Validate Timestamps: Reject webhooks with old timestamps (>5 minutes)
  • Secret Management: Securely store webhook secrets (use environment variables)
  • Rotate Secrets: Periodically rotate webhook secrets
  • Monitor Failures: Alert on repeated verification failures (potential attack)

Monitoring and Alerting

  • Track Delivery: Monitor webhook delivery success rates
  • Set Up Alerts: Alert on repeated failures or timeouts
  • Log Everything: Log all webhook events for debugging
  • Monitor Processing Time: Ensure webhooks process within timeout
  • Dashboard Review: Regularly review webhook logs in Koard Developer Portal

See also

This wraps up the webhook setup guide. See the links below for related information: