Webhook API Documentation

TIB Financial Solutions

Version 1.0.0 | October 3, 2025

Table of Contents

1. Overview

What is a webhook?

TIB webhooks allow your system to receive real-time notifications about transaction events (payments, deposits, status changes, etc.).

How it works:
TIB Transaction → Event Triggered → POST Webhook → Your Endpoint → Processing

Prerequisites

2. Initial Configuration

Step 1: Create Your Endpoint

Create a public URL that will receive webhooks:

Example: https://your-domain.com/api/webhooks/tib/transaction

Recommendations:

Step 2: Provide URL to TIB

Contact your TIB representative and provide the complete URL of your endpoint.

Step 3: Network Security Configuration (MANDATORY)

IMPORTANT: Mandatory Security
To ensure that only TIB servers can send webhooks to your endpoint, you must configure your network infrastructure to allow only our IP address range.
IP Range to Allow:
204.225.113.0/24

This configuration must be performed on your network infrastructure (firewall, security groups, WAF, reverse proxy, etc.) according to your tools and policies.

Why This is Essential

Without this filtering, anyone on the Internet could send fake requests to your endpoint and potentially manipulate your financial data.

3. Security and Authentication

Recommended Multi-Layer Protection

Layer Description Priority
IP Filtering Allow only 204.225.113.0/24 MANDATORY
HTTPS Data encryption in transit Strongly Recommended
Payload Validation Verify JSON structure Recommended
Logging Record all received webhooks Recommended

Request Format

4. Payload Structure

General Format

POST /api/webhooks/tib/transaction HTTP/1.1
Host: your-domain.com
Content-Type: application/json

{
  "EventName": "TransactionEvent",
  "Target": "Client",
  "Direction": "Collect",
  ...
}

Main Properties

TransactionEventPayload

Property Type Description Possible Values
EventName string Event name "TransactionEvent"
Target string Transaction target "Client" / "Merchant"
Direction string Financial flow direction "Collect" / "Deposit"
Step string Process step "Validation" / "Transmission" / "StatusCheck" / "Payback"
Status string Event status See Status section
DescriptionCode string Description code "0" to "7"
Description string Internal technical description For debugging
Amount string Formatted amount Ex: "123.45 $"
MerchantId string (GUID) Unique merchant identifier
MerchantName string Merchant name
MerchantAccountPreview string Merchant account preview Ex: "***-*****-**1111"
MerchantExternalId string External merchant ID
MerchantDescription string Merchant description
TransferPayload array List of transfers See TransferPayload

TransferPayload

Property Type Description
TransferId string (GUID) Unique transfer identifier
Amount decimal Transfer amount
OperationKind int Operation type (see OperationKind enum)
OriginalAmount decimal Original amount
ExternalReferenceId string External reference defined by you
DueDate string Due date - Format: "MM/DD/YYYY HH:MM:SS AM/PM"
CreatedDate string Creation date
PaymentMethodId string (GUID) Payment method ID
PaymentMethodDescription string Method description
PaymentMethodPreview string Masked preview
CustomerId string (GUID) Customer ID
CustomerName string Customer name
CustomerExternalId string Your external customer ID
BillAmount string Bill amount
BillCurrency string "1" (CAD) / "2" (USD) / "3" (EUR) / "4" (GBP)
BillDescription string Bill description
BillId string (GUID) Bill ID
BillLanguage string "1" (French) / "2" (English)
BillTitle string Bill title
BillCreatedDate string Bill creation date
ExternalSystemBillNumber1 string External system bill number 1
ExternalSystemBillNumber2 string External system bill number 2
ExternalSystemBillNumber3 string External system bill number 3
BillMerchantId string (GUID) Merchant ID on bill
BillCustomerId string (GUID) Customer ID on bill
Payout object Payout information (US only)

5. Event Management

Possible Statuses

Status Meaning Recommended Action
Success_Success Transaction completed successfully Mark as paid
Success_NoResultReturned Success but result pending Monitor next webhooks
Success_Skip Step skipped Informational
Success_WaitManual Waiting for manual intervention Wait for TIB validation
Success_Error Partial success with error Check details
Error_Temporary Temporary error Will be retried automatically
Error_Fatal Fatal error Transaction failed permanently
Abort Transaction cancelled Transaction abandoned

Description Codes

Code Name Description
"0" NotSet Not defined
"1" Confirmed Transaction confirmed
"2" ErrorOther Other/generic error
"3" NoFund Insufficient funds
"4" AccountError Bank account error
"5" Opposition Bank opposition
"6" InteracRefused Interac transaction refused
"7" InteracFailed Interac transaction failed

OperationKind Enum

Value Name Description
-1UnknownUnknown type
0NotSetNot defined
1PaymentStandard payment
2FreeDepositFree deposit
3FreeCollectionFree collection
4FeePaymentPayment fee
5FeeFreeDepositFree deposit fee
6FeeFreeCollectionFree collection fee
10ReturnChargeReturn charge
11FileFeesFile fees
12RefundRefund
13TibCollectTIB collection
14AccountCheckNsfFeesNSF fees account check
15AccountCheckOppositionFeesOpposition fees account check
16AccountCheckNsfFeesHitNSF fees hit
17AccountCheckOppositionFeesHitOpposition fees hit
18FeeReverseChargeReverse charge fee
19RevertedOperationReverted operation
20ConvenientFeeConvenience fee
21RevertConvenientFeeRevert convenience fee
22PlatformFeePlatform fee
23GatewayUsageFeeGateway usage fee
24SupportFeeSupport fee
100GroupedFeesGrouped fees
101FeesRetransferFees retransfer

Typical Event Flow

Scenario 1: Normal Transaction (Successful)

1. Validation → Step: Validation, Status: Success_Skip
2. Transmission → Step: Transmission, Status: Success_Success
3. StatusCheck → Step: StatusCheck, Status: Success_NoResultReturned

Scenario 2: NSF (Non-Sufficient Funds)

1. Validation → Status: Success_Skip
2. Transmission → Status: Success_Success
3. StatusCheck → Status: Success_NoResultReturned
4. Payback → Status: Success_Error, DescriptionCode: "3" (NoFund)

Scenario 3: Opposition (Account Opposition)

1. Validation → Status: Success_Skip
2. Transmission → Status: Success_Success
3. StatusCheck → Status: Success_Error, DescriptionCode: "5" (Opposition)

Scenario 4: Account Closed

1. Validation → Status: Success_Skip
2. Transmission → Status: Success_Success
3. Payback → Status: Success_Error, DescriptionCode: "4" (AccountError)
Important - Payback Check Validation Period:

After a successful transaction, TIB validates the payment during these periods. If an issue occurs (NSF, opposition, closed account), you will receive a Payback webhook event.

6. Recommended Implementation

Recommended Architecture

Internet
   ↓
Firewall (Allow 204.225.113.0/24)
   ↓
Endpoint /webhooks
   ↓
Validate Payload + Log
   ↓
Return 200 OK (IMPORTANT)
   ↓
Asynchronous Processing

Golden Rules

1. Respond Quickly with 200 OK

Your endpoint MUST return HTTP 200 OK in less than 30 seconds.

GOOD:
[HttpPost("webhooks/tib/transaction")]
public IActionResult ReceiveWebhook([FromBody] TransactionEventPayload payload)
{
    _ = ProcessInBackground(payload);
    return Ok();
}
BAD:
[HttpPost("webhooks/tib/transaction")]
public IActionResult ReceiveWebhook([FromBody] TransactionEventPayload payload)
{
    ProcessTransaction(payload); // Can take too long
    return Ok();
}

2. Log All Events

_logger.LogInformation(
    "Webhook received - TransferId: {TransferId}, Status: {Status}, Amount: {Amount}",
    transferId, 
    payload.Status, 
    payload.Amount
);

3. Only Return 200 OK if Webhook is Properly Received

try
{
    await SaveWebhook(payload);
    return Ok();
}
catch (Exception ex)
{
    _logger.LogError(ex, "Unable to save webhook");
    
    // Do not return 200 OK
    // TIB will know your server is down
    return StatusCode(500);
}

7. Error Handling

Behavior on Error

If your endpoint does not respond with 200 OK, TIB considers your server is down and temporarily stops sending webhooks.

TIB sends webhook
   ↓
Your endpoint responds 200 OK → Success, end
   ↓
No 200 OK (500, timeout, etc.) → Server considered down

HTTP Response Codes

Code Meaning TIB Action
200 OK Webhook received and accepted Success
201, 202, 204 Accepted (variants) Success
400 Invalid request Server down
401, 403 Unauthorized Server down
404 Endpoint not found Server down
500, 502, 503 Server error Server down
Timeout No response Server down
IMPORTANT: No webhook is resent. If your server is down, events will not be delivered during this period.

8. Code Examples

Complete C# Example (.NET Core)

using Microsoft.AspNetCore.Mvc;
using System.Text.Json;

namespace YourApp.Controllers
{
    [ApiController]
    [Route("api/webhooks/tib")]
    public class TibWebhookController : ControllerBase
    {
        private readonly ILogger<TibWebhookController> _logger;
        private readonly ApplicationDbContext _db;
        private readonly IBackgroundJobQueue _queue;

        public TibWebhookController(
            ILogger<TibWebhookController> logger,
            ApplicationDbContext db,
            IBackgroundJobQueue queue)
        {
            _logger = logger;
            _db = db;
            _queue = queue;
        }

        [HttpPost("transaction")]
        public async Task<IActionResult> ReceiveTransactionEvent(
            [FromBody] TransactionEventPayload payload)
        {
            try
            {
                // 1. Validate payload
                if (payload?.TransferPayload == null || !payload.TransferPayload.Any())
                {
                    _logger.LogWarning("Webhook received with invalid payload");
                    return BadRequest("Invalid payload");
                }

                var transferId = payload.TransferPayload.First().TransferId;

                // 2. Log receipt
                _logger.LogInformation(
                    "Webhook received - Event: {EventName}, TransferId: {TransferId}, " +
                    "Status: {Status}, Amount: {Amount}",
                    payload.EventName,
                    transferId,
                    payload.Status,
                    payload.Amount
                );

                // 3. Save immediately
                var webhookEvent = new WebhookEvent
                {
                    Id = Guid.NewGuid(),
                    TransferId = transferId,
                    EventName = payload.EventName,
                    Status = payload.Status,
                    RawPayload = JsonSerializer.Serialize(payload),
                    ReceivedAt = DateTime.UtcNow
                };

                await _db.WebhookEvents.AddAsync(webhookEvent);
                await _db.SaveChangesAsync();

                // 4. Queue for async processing
                _queue.QueueBackgroundWorkItem(async token =>
                {
                    await ProcessWebhookAsync(payload, webhookEvent.Id, token);
                });

                // 5. IMPORTANT: Return 200 OK immediately
                return Ok(new 
                { 
                    message = "Webhook received successfully",
                    transferId
                });
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error receiving webhook");
                return StatusCode(500, "Internal error");
            }
        }

        private async Task ProcessWebhookAsync(
            TransactionEventPayload payload, 
            Guid eventId,
            CancellationToken cancellationToken)
        {
            try
            {
                _logger.LogInformation("Processing started - EventId: {EventId}", eventId);

                switch (payload.Status)
                {
                    case "Success_Success":
                        await ProcessSuccessfulPayment(payload);
                        break;

                    case "Error_Fatal":
                        await ProcessFailedPayment(payload);
                        break;

                    default:
                        _logger.LogInformation("Status: {Status}", payload.Status);
                        break;
                }

                _logger.LogInformation("Processing completed - EventId: {EventId}", eventId);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing - EventId: {EventId}", eventId);
            }
        }

        private async Task ProcessSuccessfulPayment(TransactionEventPayload payload)
        {
            var transfer = payload.TransferPayload.First();
            
            _logger.LogInformation(
                "Successful payment - Customer: {CustomerName}, Amount: {Amount}",
                transfer.CustomerName,
                transfer.Amount
            );

            // Update your system
            var billId = transfer.BillId;
            var invoice = await _db.Invoices
                .FirstOrDefaultAsync(i => i.ExternalBillId == billId);
            
            if (invoice != null)
            {
                invoice.Status = "Paid";
                invoice.PaidAt = DateTime.UtcNow;
                invoice.PaidAmount = transfer.Amount ?? 0;
                await _db.SaveChangesAsync();
            }
        }

        private async Task ProcessFailedPayment(TransactionEventPayload payload)
        {
            var transfer = payload.TransferPayload.First();
            
            _logger.LogWarning(
                "Failed payment - Customer: {CustomerName}, Code: {DescriptionCode}",
                transfer.CustomerName,
                payload.DescriptionCode
            );
        }
    }
}

Data Models

public class WebhookEvent
{
    public Guid Id { get; set; }
    public string TransferId { get; set; }
    public string EventName { get; set; }
    public string Status { get; set; }
    public string RawPayload { get; set; }
    public DateTime ReceivedAt { get; set; }
}

public class TransactionEventPayload
{
    public string EventName { get; set; }
    public string Target { get; set; }
    public string Direction { get; set; }
    public string Step { get; set; }
    public string Status { get; set; }
    public string DescriptionCode { get; set; }
    public string Description { get; set; }
    public string Amount { get; set; }
    public string MerchantId { get; set; }
    public string MerchantName { get; set; }
    public string MerchantAccountPreview { get; set; }
    public string MerchantExternalId { get; set; }
    public string MerchantDescription { get; set; }
    public List<TransferPayload> TransferPayload { get; set; }
}

public class TransferPayload
{
    public string TransferId { get; set; }
    public decimal? Amount { get; set; }
    public int? OperationKind { get; set; }
    public decimal OriginalAmount { get; set; }
    public string ExternalReferenceId { get; set; }
    public string DueDate { get; set; }
    public string CreatedDate { get; set; }
    public string PaymentMethodId { get; set; }
    public string PaymentMethodDescription { get; set; }
    public string PaymentMethodPreview { get; set; }
    public string CustomerId { get; set; }
    public string CustomerName { get; set; }
    public string CustomerExternalId { get; set; }
    public string BillAmount { get; set; }
    public string BillCurrency { get; set; }
    public string BillDescription { get; set; }
    public string BillId { get; set; }
    public string BillLanguage { get; set; }
    public string BillTitle { get; set; }
    public string BillCreatedDate { get; set; }
    public string ExternalSystemBillNumber1 { get; set; }
    public string ExternalSystemBillNumber2 { get; set; }
    public string ExternalSystemBillNumber3 { get; set; }
    public string BillMerchantId { get; set; }
    public string BillCustomerId { get; set; }
    public PayoutPayload Payout { get; set; }
}

Node.js / Express Example

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.json());

app.post('/api/webhooks/tib/transaction', async (req, res) => {
    try {
        const payload = req.body;
        
        // 1. Validate payload
        if (!payload.TransferPayload || payload.TransferPayload.length === 0) {
            console.warn('Invalid payload received');
            return res.status(400).json({ error: 'Invalid payload' });
        }
        
        const transferId = payload.TransferPayload[0].TransferId;
        
        // 2. Log
        console.log(`Webhook received - TransferId: ${transferId}, Status: ${payload.Status}`);
        
        // 3. Save
        await saveWebhook(payload);
        
        // 4. Process asynchronously
        processWebhookAsync(payload).catch(err => {
            console.error('Async processing error:', err);
        });
        
        // 5. IMPORTANT: Respond 200 OK immediately
        return res.status(200).json({ 
            message: 'Webhook received successfully', 
            transferId 
        });
        
    } catch (error) {
        console.error('Critical error:', error);
        return res.status(500).json({ error: 'Internal error' });
    }
});

async function processWebhookAsync(payload) {
    console.log('Async processing started');
    
    switch(payload.Status) {
        case 'Success_Success':
            await handleSuccessfulPayment(payload);
            break;
        case 'Error_Fatal':
            await handleFailedPayment(payload);
            break;
    }
    
    console.log('Processing completed');
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Webhook server started on port ${PORT}`);
});

Python / Flask Example

from flask import Flask, request, jsonify
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

@app.route('/api/webhooks/tib/transaction', methods=['POST'])
def receive_webhook():
    try:
        payload = request.get_json()
        
        # 1. Validate
        if not payload.get('TransferPayload'):
            logging.warning('Invalid payload')
            return jsonify({'error': 'Invalid payload'}), 400
        
        transfer_id = payload['TransferPayload'][0]['TransferId']
        
        # 2. Log
        logging.info(f"Webhook received - TransferId: {transfer_id}, "
                    f"Status: {payload['Status']}")
        
        # 3. Save
        save_webhook(payload)
        
        # 4. Process asynchronously
        process_webhook(payload)
        
        # 5. Return 200 OK immediately
        return jsonify({
            'message': 'Webhook received successfully',
            'transferId': transfer_id
        }), 200
        
    except Exception as e:
        logging.error(f"Critical error: {str(e)}")
        return jsonify({'error': 'Internal error'}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

9. Testing and Validation

Pre-Production Checklist

Run every scenario below against the TIB sandbox environment before go-live. Webhooks fire for every rail listed on our payment methods page, and our team can wire them into your systems through a business integration.

Test Scenarios

Test 1: Valid Webhook

Request:

POST /api/webhooks/tib/transaction
{
  "EventName": "TransactionEvent",
  "Target": "Client",
  "Direction": "Collect",
  "Step": "StatusCheck",
  "Status": "Success_Success",
  "Amount": "100.00 $",
  "TransferPayload": [{
    "TransferId": "test-guid-123",
    "Amount": 100.00,
    "CustomerName": "Test User"
  }]
}

Expected result:

Test 2: Invalid Webhook

Request:

POST /api/webhooks/tib/transaction
{
  "EventName": "TransactionEvent"
}

Expected result:

Test 3: Server Error

Simulation: Turn off your database

Expected result:

10. FAQ and Troubleshooting

Frequently Asked Questions

Q: What happens if my server is down?

A: TIB will detect that your server does not respond with 200 OK and will temporarily stop sending webhooks. Events during this period will not be delivered.

Q: Are webhooks resent on error?

A: Yes. If your endpoint does not respond with 200 OK, TIB considers your server is down and temporarily stops sending webhooks. When your server responds again with a 200 OK, webhooks will be delivered again.

Q: How can I recover missed webhooks?

A: Contact your TIB representative to discuss options for recovering missed events.

Q: Can I have multiple endpoints?

A: Contact your TIB representative to discuss this possibility.

Q: Are webhooks sent in order?

A:Webhooks are sent in chronological order of events. However, due to asynchronous processing and network conditions, we cannot guarantee that you will receive them in the same order.

Q: Can I receive multiple webhooks for the same transaction?

A: Yes, you can receive multiple webhooks for different steps (Validation, Transmission, StatusCheck, etc.).

Q: How to handle multiple currencies?

A: Use the BillCurrency field in TransferPayload to identify the currency.

Q: Do webhooks contain sensitive data?

A: Bank accounts are masked. No complete card numbers are transmitted.

Common Problems

Problem: I don't receive webhooks

Solutions:

Problem: My endpoint times out

Solutions:

Problem: 401/403 error on my endpoint

Solutions:

11. Support and Contact

Get Help

TIB Technical Support:

Information to Provide

For effective support, include:

  1. System Information
  2. Problem Description
  3. Examples

Complete Payload Example

{
  "Target": "Client",
  "Direction": "Collect",
  "Step": "StatusCheck",
  "Status": "Success_Success",
  "DescriptionCode": "1",
  "Description": "Transaction confirmed",
  "Amount": "123.45 $",
  "MerchantId": "00000000-0000-0000-0000-000000000000",
  "MerchantName": "Your Company Inc.",
  "MerchantAccountPreview": "***-*****-**1111",
  "MerchantExternalId": "MERCHANT-001",
  "MerchantDescription": "Service company",
  "TransferPayload": [
    {
      "TransferId": "11111111-1111-1111-1111-111111111111",
      "Amount": 123.45,
      "OperationKind": 1,
      "OriginalAmount": 123.45,
      "ExternalReferenceId": "INVOICE-2025-001",
      "DueDate": "10/3/2025 12:00:00 PM",
      "CreatedDate": "10/1/2025 10:00:00 AM",
      "PaymentMethodId": "22222222-2222-2222-2222-222222222222",
      "PaymentMethodDescription": "Bank account",
      "PaymentMethodPreview": "***-*****-***1111",
      "CustomerId": "33333333-3333-3333-3333-333333333333",
      "CustomerName": "John Smith",
      "CustomerExternalId": "CLIENT-12345",
      "BillAmount": "123.45 $",
      "BillCurrency": "1",
      "BillDescription": "Monthly invoice - October 2025",
      "BillId": "44444444-4444-4444-4444-444444444444",
      "BillLanguage": "2",
      "BillTitle": "Invoice #2025-001",
      "BillCreatedDate": "10/1/2025 9:00:00 AM",
      "ExternalSystemBillNumber1": "INV-2025-001",
      "ExternalSystemBillNumber2": "DEPT-A-001",
      "ExternalSystemBillNumber3": "",
      "BillMerchantId": "00000000-0000-0000-0000-000000000000",
      "BillCustomerId": "33333333-3333-3333-3333-333333333333",
      "Payout": null
    }
  ],
  "EventName": "TransactionEvent"
}

Summary - Key Points

TO DO

  1. Configure your network infrastructure to allow only 204.225.113.0/24
  2. Respond with 200 OK immediately (less than 30 seconds)
  3. Process asynchronously
  4. Log all events
  5. Use HTTPS
  6. Test before production

TO AVOID

  1. Synchronous processing
  2. Exposing endpoint without IP filtering
  3. Returning 200 OK if webhook not saved
  4. Ignoring errors and logs