Webhook Events: Handling Real-Time Payment Notifications

Webhook Events: Handling Real-Time Payment Notifications

Table of Contents

Payments are inherently asynchronous. A card charge you initiate may succeed immediately — or it may pend, require 3DS authentication, trigger a fraud review, or result in a chargeback days later. If your application only reacts to the synchronous API response when you create a transaction, you will miss a large portion of payment lifecycle events that affect your business logic, customer experience, and financial reconciliation.

Webhooks are TIB Finance's mechanism for pushing real-time event notifications to your application the moment something significant happens. This guide covers everything you need to implement a robust, secure webhook handler. For broader API integration context, see the Payment API Integration Guide.

What Are Webhooks?

A webhook is an HTTP POST request sent by TIB Finance to a URL that you specify (your webhook endpoint) when a specific event occurs. Think of it as TIB Finance calling your application and saying "something just happened — here are the details."

The alternative — polling — involves your application repeatedly calling the TIB Finance API asking "did anything change?" Polling is inefficient, adds latency to your event processing, and creates unnecessary API load. Webhooks eliminate all three problems by delivering events at the moment they occur.

Setting Up Your Webhook Endpoint

Configure your webhook endpoint URL in the TIB Finance merchant dashboard under Settings > Webhooks. Your endpoint must:

  • Be publicly accessible via HTTPS (not HTTP)
  • Accept HTTP POST requests
  • Return HTTP 200 (or any 2xx status) within 10 seconds
  • Accept the application/json content type

You can register multiple webhook endpoints and filter which event types each endpoint receives — for example, sending payment events to your order management system and dispute events to your finance team's system.

Event Types Reference

TIB Finance sends webhooks for the following event categories:

Payment Events

Event TypeDescription
payment.succeeded A payment was successfully processed and funds will be captured. Trigger order fulfillment.
payment.failed A payment attempt was declined or failed. Notify the customer to try a different card.
payment.pending Payment is pending processing (e.g., ACH, bank transfer). Do not fulfill until payment.succeeded.
payment.captured A previously authorized (auth-only) payment has been captured.
payment.cancelled An authorized payment was cancelled before capture.

Refund Events

Event TypeDescription
refund.created A refund has been initiated. Update your records but wait for refund.succeeded.
refund.succeeded Refund has been processed successfully and funds returned to the customer.
refund.failed A refund failed to process. Manual intervention may be required.

Dispute Events

Event TypeDescription
dispute.created A chargeback or dispute has been filed. Respond within the deadline.
dispute.updated A dispute's status has changed (e.g., evidence submitted).
dispute.closed A dispute has been resolved. Check outcome field for won/lost.

Webhook Payload Structure

All TIB Finance webhook payloads share a common envelope structure:

{ "id": "evt_2Mc5pR7nQtL3wJ9v", // Unique event ID "type": "payment.succeeded", // Event type "created_at": "2024-12-01T14:32:11Z", // ISO 8601 timestamp "livemode": true, // false in sandbox "data": { "object": { "id": "txn_9Yc4oQ8nRsM3wL5z", "amount": 4999, "currency": "USD", "status": "succeeded", "reference": "ORDER-20241201-001", "payment_method": { "token": "pm_3Kd9fN7mQpJ2vH4x", "last4": "1111", "brand": "visa" } } } }

Always use the id field in the data.object to fetch the authoritative object from the API if you need complete, up-to-date details. Do not rely on cached data from earlier API calls.

Signature Verification

Every webhook request from TIB Finance includes a TIB-Signature HTTP header containing an HMAC-SHA256 signature of the request body, signed with your webhook secret. Always verify this signature before processing the event. Skipping signature verification exposes your application to replay attacks and spoofed events.

Security Critical

If you process a webhook without verifying the signature, an attacker could send a fake "payment.succeeded" event to your endpoint and trigger order fulfillment without any actual payment. Signature verification is non-negotiable.

// Node.js signature verification example const crypto = require('crypto'); function verifyWebhookSignature(payload, signature, secret) { // The signature header format is: t=timestamp,v1=signature const parts = signature.split(','); const timestamp = parts[0].split('=')[1]; const receivedSig = parts[1].split('=')[1]; // Verify the timestamp is within 5 minutes to prevent replay attacks const tolerance = 300; // 5 minutes in seconds if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > tolerance) { throw new Error('Webhook timestamp too old'); } // Compute expected signature const signedPayload = `${timestamp}.${payload}`; const expectedSig = crypto .createHmac('sha256', secret) .update(signedPayload) .digest('hex'); // Use constant-time comparison to prevent timing attacks return crypto.timingSafeEqual( Buffer.from(receivedSig), Buffer.from(expectedSig) ); }

Retry Logic

TIB Finance automatically retries webhook deliveries when your endpoint does not return a 2xx response. Understanding the retry schedule helps you design robust error handling:

  • Immediate retry: If your endpoint returns a 5xx error or times out, TIB Finance retries after 30 seconds.
  • Exponential backoff: Subsequent retries use exponential backoff: 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours, 24 hours.
  • Maximum attempts: TIB Finance will attempt delivery up to 10 times over approximately 72 hours.
  • Abandonment: After 10 failed attempts, the event is marked as failed. You can view and manually replay failed events from the merchant dashboard.

Design your webhook handler to return 200 immediately upon receipt, before doing any complex processing. Acknowledge receipt first, then process asynchronously using a queue or background job. This prevents timeouts that cause unnecessary retries.

// Correct pattern: acknowledge first, process async app.post('/webhook', (req, res) => { // Verify signature first const isValid = verifyWebhookSignature( req.rawBody, req.headers['tib-signature'], process.env.WEBHOOK_SECRET ); if (!isValid) return res.status(401).send('Invalid signature'); // Acknowledge immediately res.status(200).send('OK'); // Process asynchronously (queue the event) eventQueue.push(req.body); });

Idempotency

Due to the retry mechanism, your webhook handler must be idempotent — processing the same event multiple times must produce the same result as processing it once. Without idempotency, retried events can cause duplicate order fulfillments, duplicate email sends, or incorrect financial records.

Implement idempotency by tracking processed event IDs:

async function processWebhookEvent(event) { // Check if we've already processed this event const existing = await db.processedEvents.findOne({ event_id: event.id }); if (existing) { console.log(`Event ${event.id} already processed, skipping`); return; } // Process the event if (event.type === 'payment.succeeded') { await fulfillOrder(event.data.object.reference); await sendConfirmationEmail(event.data.object); } // Mark as processed AFTER successful processing await db.processedEvents.insert({ event_id: event.id, processed_at: new Date() }); }

Best Practices

A summary of key recommendations for a production-grade webhook implementation:

  1. Always verify signatures. Reject any webhook that fails signature verification with HTTP 401.
  2. Respond within 10 seconds. Acknowledge first, process asynchronously using a job queue.
  3. Implement idempotency. Store processed event IDs and skip duplicates.
  4. Use HTTPS only. Never configure a webhook endpoint on an HTTP URL.
  5. Fetch fresh data from the API. If you need complete object details, fetch them via API using the object ID in the event rather than trusting only the webhook payload.
  6. Monitor for failures. Set up alerting for failed webhook deliveries, especially for critical events like payment.succeeded and dispute.created.
  7. Test in the sandbox. The TIB Finance sandbox allows you to manually trigger any event type to test your handler. See the sandbox testing guide.
  8. Handle all event types gracefully. Your handler will receive event types you may not have explicitly handled. Always have a default case that returns 200 to prevent unnecessary retries for events you intentionally ignore.
  9. Log everything. Log the raw payload, signature verification result, and your processing outcome for every webhook received. This is essential for debugging and financial reconciliation.
  10. Use separate endpoints per environment. Never send production webhooks to a development or staging endpoint, and vice versa.

Build Your Webhook Integration

The TIB Finance sandbox lets you test all webhook event types before going live. Access the developer docs and sandbox environment to get started.

API Docs Sandbox