Building Real-Time CargoWise Integrations with Webhooks (2025 Guide)

Jan 19, 2025
cargowisewebhooksreal-timeintegration
0

Real-time integration is becoming increasingly critical in modern freight forwarding operations. While traditional polling methods and batch processing have their place, webhooks provide the most efficient way to receive instant notifications about CargoWise events, enabling faster response times and more responsive applications.

This comprehensive guide covers everything you need to know about implementing CargoWise webhook integrations, from basic setup to advanced security and error handling patterns. Whether you're building a customer portal, tracking system, or automated workflow, this guide will help you create robust, real-time integrations.

Understanding CargoWise Webhooks

What are Webhooks?

Webhooks are HTTP callbacks that CargoWise sends to your application when specific events occur. Instead of your application constantly polling CargoWise for updates, CargoWise proactively notifies your system about changes, making integrations more efficient and responsive.

Key Benefits:

  • Real-time Notifications: Instant event delivery
  • Reduced API Calls: No need for constant polling
  • Better Performance: Lower resource usage
  • Improved User Experience: Faster response times
  • Cost Effective: Reduced API usage costs

CargoWise Webhook Architecture

Event Flow:

  1. Event Occurs in CargoWise (shipment created, status updated, etc.)
  2. Webhook Triggered by CargoWise system
  3. HTTP POST sent to your webhook endpoint
  4. Your Application processes the event
  5. Response Sent back to CargoWise (acknowledgment)

Webhook Components:

  • Event Types: Different types of events (shipment, consol, forward, etc.)
  • Payload: JSON data containing event information
  • Headers: Authentication and metadata
  • Retry Logic: Automatic retry on failures
  • Security: Signature verification and authentication

Setting Up CargoWise Webhooks

Prerequisites

1. CargoWise Webhook Access

  • Active CargoWise subscription
  • Webhook permissions enabled
  • API credentials configured

2. Webhook Endpoint

  • Publicly accessible HTTPS endpoint
  • SSL certificate (required for security)
  • Proper error handling and logging

3. Development Environment

  • CargoWise sandbox access
  • Webhook testing tools
  • Local development setup

Basic Webhook Configuration

CargoWise Webhook Setup:

{
  "webhook_url": "https://your-domain.com/api/cargowise/webhook",
  "events": [
    "shipment.created",
    "shipment.updated",
    "shipment.status_changed",
    "consol.created",
    "consol.updated",
    "forward.created",
    "forward.updated"
  ],
  "authentication": {
    "type": "signature",
    "secret": "your-webhook-secret"
  },
  "retry_policy": {
    "max_retries": 3,
    "retry_delay": 5000
  }
}

Webhook Endpoint Implementation (C#):

[ApiController]
[Route("api/cargowise")]
public class CargoWiseWebhookController : ControllerBase
{
    private readonly ILogger<CargoWiseWebhookController> _logger;
    private readonly IWebhookProcessor _webhookProcessor;
    private readonly IWebhookValidator _webhookValidator;
    
    public CargoWiseWebhookController(
        ILogger<CargoWiseWebhookController> logger,
        IWebhookProcessor webhookProcessor,
        IWebhookValidator webhookValidator)
    {
        _logger = logger;
        _webhookProcessor = webhookProcessor;
        _webhookValidator = webhookValidator;
    }
    
    [HttpPost("webhook")]
    public async Task<IActionResult> HandleWebhook([FromBody] CargoWiseWebhookPayload payload)
    {
        try
        {
            // Validate webhook signature
            if (!await _webhookValidator.ValidateSignature(Request))
            {
                _logger.LogWarning("Invalid webhook signature");
                return Unauthorized();
            }
            
            // Process webhook payload
            await _webhookProcessor.ProcessWebhookAsync(payload);
            
            _logger.LogInformation("Webhook processed successfully: {EventType}", payload.EventType);
            return Ok();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing webhook: {EventType}", payload?.EventType);
            return StatusCode(500, "Internal server error");
        }
    }
}

Webhook Endpoint Implementation (Python):

from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import logging

app = Flask(__name__)
logger = logging.getLogger(__name__)

@app.route('/api/cargowise/webhook', methods=['POST'])
async def handle_webhook():
    try:
        # Get webhook payload
        payload = request.get_json()
        
        # Validate webhook signature
        if not validate_webhook_signature(request):
            logger.warning("Invalid webhook signature")
            return jsonify({"error": "Unauthorized"}), 401
        
        # Process webhook payload
        await process_webhook_payload(payload)
        
        logger.info(f"Webhook processed successfully: {payload.get('event_type')}")
        return jsonify({"status": "success"}), 200
        
    except Exception as e:
        logger.error(f"Error processing webhook: {str(e)}")
        return jsonify({"error": "Internal server error"}), 500

def validate_webhook_signature(request):
    """Validate webhook signature for security"""
    signature = request.headers.get('X-CargoWise-Signature')
    if not signature:
        return False
    
    # Get webhook secret from environment
    webhook_secret = os.getenv('CARGOWISE_WEBHOOK_SECRET')
    
    # Calculate expected signature
    expected_signature = hmac.new(
        webhook_secret.encode('utf-8'),
        request.data,
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected_signature)

CargoWise Event Types

Shipment Events

Shipment Created Event:

{
  "event_type": "shipment.created",
  "event_id": "evt_1234567890",
  "timestamp": "2025-01-19T10:00:00Z",
  "data": {
    "shipment_id": "SH-2025-001",
    "mode": "Air",
    "origin": {
      "city": "New York",
      "country": "US",
      "airport": "JFK"
    },
    "destination": {
      "city": "London",
      "country": "GB",
      "airport": "LHR"
    },
    "cargo": {
      "description": "Electronics",
      "weight": 150.5,
      "volume": 2.3,
      "pieces": 5
    },
    "status": "Created",
    "created_by": "user@company.com",
    "created_at": "2025-01-19T10:00:00Z"
  }
}

Shipment Status Changed Event:

{
  "event_type": "shipment.status_changed",
  "event_id": "evt_1234567891",
  "timestamp": "2025-01-19T11:30:00Z",
  "data": {
    "shipment_id": "SH-2025-001",
    "previous_status": "Created",
    "new_status": "In Transit",
    "status_reason": "Aircraft departed",
    "location": {
      "city": "New York",
      "country": "US",
      "airport": "JFK"
    },
    "updated_by": "system",
    "updated_at": "2025-01-19T11:30:00Z"
  }
}

Shipment Updated Event:

{
  "event_type": "shipment.updated",
  "event_id": "evt_1234567892",
  "timestamp": "2025-01-19T12:00:00Z",
  "data": {
    "shipment_id": "SH-2025-001",
    "changes": {
      "cargo": {
        "weight": {
          "old_value": 150.5,
          "new_value": 155.0
        }
      },
      "destination": {
        "airport": {
          "old_value": "LHR",
          "new_value": "LGW"
        }
      }
    },
    "updated_by": "user@company.com",
    "updated_at": "2025-01-19T12:00:00Z"
  }
}

Consol Events

Consol Created Event:

{
  "event_type": "consol.created",
  "event_id": "evt_1234567893",
  "timestamp": "2025-01-19T13:00:00Z",
  "data": {
    "consol_id": "CON-2025-001",
    "master_awb": "123-45678901",
    "house_awbs": [
      "123-45678902",
      "123-45678903",
      "123-45678904"
    ],
    "consolidator": {
      "name": "ABC Freight Forwarders",
      "code": "ABC"
    },
    "status": "Created",
    "created_by": "user@company.com",
    "created_at": "2025-01-19T13:00:00Z"
  }
}

Forward Events

Forward Created Event:

{
  "event_type": "forward.created",
  "event_id": "evt_1234567894",
  "timestamp": "2025-01-19T14:00:00Z",
  "data": {
    "forward_id": "FWD-2025-001",
    "shipment_id": "SH-2025-001",
    "instructions": {
      "text": "Deliver to warehouse 3, dock 2",
      "priority": "High"
    },
    "routing": {
      "next_stop": "Warehouse 3",
      "contact": "John Smith",
      "phone": "+1-555-0123"
    },
    "status": "Created",
    "created_by": "user@company.com",
    "created_at": "2025-01-19T14:00:00Z"
  }
}

Webhook Security Implementation

Signature Verification

C# Signature Verification:

public class CargoWiseWebhookValidator
{
    private readonly string _webhookSecret;
    private readonly ILogger<CargoWiseWebhookValidator> _logger;
    
    public CargoWiseWebhookValidator(IConfiguration configuration, ILogger<CargoWiseWebhookValidator> logger)
    {
        _webhookSecret = configuration["CargoWise:WebhookSecret"];
        _logger = logger;
    }
    
    public async Task<bool> ValidateSignature(HttpRequest request)
    {
        try
        {
            var signature = request.Headers["X-CargoWise-Signature"].FirstOrDefault();
            if (string.IsNullOrEmpty(signature))
            {
                _logger.LogWarning("Missing webhook signature");
                return false;
            }
            
            // Read request body
            request.Body.Position = 0;
            var body = await new StreamReader(request.Body).ReadToEndAsync();
            
            // Calculate expected signature
            var expectedSignature = CalculateSignature(body, _webhookSecret);
            
            // Compare signatures
            return string.Equals(signature, expectedSignature, StringComparison.OrdinalIgnoreCase);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error validating webhook signature");
            return false;
        }
    }
    
    private string CalculateSignature(string payload, string secret)
    {
        using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)))
        {
            var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
            return Convert.ToHexString(hash).ToLower();
        }
    }
}

Python Signature Verification:

import hmac
import hashlib
import os
from typing import Optional

class CargoWiseWebhookValidator:
    def __init__(self, webhook_secret: str):
        self.webhook_secret = webhook_secret
    
    def validate_signature(self, payload: str, signature: str) -> bool:
        """Validate webhook signature"""
        if not signature:
            return False
        
        # Calculate expected signature
        expected_signature = hmac.new(
            self.webhook_secret.encode('utf-8'),
            payload.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        
        # Compare signatures securely
        return hmac.compare_digest(signature, expected_signature)
    
    def validate_request(self, request) -> bool:
        """Validate webhook request"""
        signature = request.headers.get('X-CargoWise-Signature')
        if not signature:
            return False
        
        payload = request.get_data(as_text=True)
        return self.validate_signature(payload, signature)

IP Whitelisting

C# IP Whitelisting:

public class CargoWiseIpWhitelistMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<CargoWiseIpWhitelistMiddleware> _logger;
    private readonly List<IPAddress> _allowedIps;
    
    public CargoWiseIpWhitelistMiddleware(RequestDelegate next, IConfiguration configuration, ILogger<CargoWiseIpWhitelistMiddleware> logger)
    {
        _next = next;
        _logger = logger;
        _allowedIps = configuration.GetSection("CargoWise:AllowedIPs")
            .Get<string[]>()
            .Select(IPAddress.Parse)
            .ToList();
    }
    
    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Path.StartsWithSegments("/api/cargowise/webhook"))
        {
            var clientIp = GetClientIpAddress(context);
            
            if (!_allowedIps.Contains(clientIp))
            {
                _logger.LogWarning("Webhook request from unauthorized IP: {ClientIp}", clientIp);
                context.Response.StatusCode = 403;
                await context.Response.WriteAsync("Forbidden");
                return;
            }
        }
        
        await _next(context);
    }
    
    private IPAddress GetClientIpAddress(HttpContext context)
    {
        // Check for forwarded IP first
        var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
        if (!string.IsNullOrEmpty(forwardedFor))
        {
            var ip = forwardedFor.Split(',')[0].Trim();
            if (IPAddress.TryParse(ip, out var parsedIp))
            {
                return parsedIp;
            }
        }
        
        // Fall back to remote IP
        return context.Connection.RemoteIpAddress;
    }
}

Rate Limiting

C# Rate Limiting:

public class CargoWiseWebhookRateLimiter
{
    private readonly MemoryCache _cache;
    private readonly int _maxRequestsPerMinute;
    private readonly ILogger<CargoWiseWebhookRateLimiter> _logger;
    
    public CargoWiseWebhookRateLimiter(int maxRequestsPerMinute = 100, ILogger<CargoWiseWebhookRateLimiter> logger = null)
    {
        _maxRequestsPerMinute = maxRequestsPerMinute;
        _logger = logger;
        _cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 1000
        });
    }
    
    public bool IsRateLimited(string clientIp)
    {
        var key = $"webhook_rate_limit_{clientIp}";
        var now = DateTime.UtcNow;
        var minute = now.ToString("yyyy-MM-dd-HH-mm");
        var cacheKey = $"{key}_{minute}";
        
        if (_cache.TryGetValue(cacheKey, out int requestCount))
        {
            if (requestCount >= _maxRequestsPerMinute)
            {
                _logger.LogWarning("Rate limit exceeded for IP: {ClientIp}", clientIp);
                return true;
            }
            
            _cache.Set(cacheKey, requestCount + 1, TimeSpan.FromMinutes(1));
        }
        else
        {
            _cache.Set(cacheKey, 1, TimeSpan.FromMinutes(1));
        }
        
        return false;
    }
}

Webhook Payload Processing

Event Processing Architecture

C# Event Processor:

public interface IWebhookProcessor
{
    Task ProcessWebhookAsync(CargoWiseWebhookPayload payload);
}

public class CargoWiseWebhookProcessor : IWebhookProcessor
{
    private readonly ILogger<CargoWiseWebhookProcessor> _logger;
    private readonly IServiceProvider _serviceProvider;
    
    public CargoWiseWebhookProcessor(ILogger<CargoWiseWebhookProcessor> logger, IServiceProvider serviceProvider)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;
    }
    
    public async Task ProcessWebhookAsync(CargoWiseWebhookPayload payload)
    {
        try
        {
            _logger.LogInformation("Processing webhook event: {EventType}", payload.EventType);
            
            // Get appropriate handler for event type
            var handler = GetEventHandler(payload.EventType);
            if (handler == null)
            {
                _logger.LogWarning("No handler found for event type: {EventType}", payload.EventType);
                return;
            }
            
            // Process event
            await handler.HandleAsync(payload);
            
            _logger.LogInformation("Webhook event processed successfully: {EventType}", payload.EventType);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing webhook event: {EventType}", payload.EventType);
            throw;
        }
    }
    
    private IWebhookEventHandler GetEventHandler(string eventType)
    {
        return eventType switch
        {
            "shipment.created" => _serviceProvider.GetService<ShipmentCreatedEventHandler>(),
            "shipment.updated" => _serviceProvider.GetService<ShipmentUpdatedEventHandler>(),
            "shipment.status_changed" => _serviceProvider.GetService<ShipmentStatusChangedEventHandler>(),
            "consol.created" => _serviceProvider.GetService<ConsolCreatedEventHandler>(),
            "consol.updated" => _serviceProvider.GetService<ConsolUpdatedEventHandler>(),
            "forward.created" => _serviceProvider.GetService<ForwardCreatedEventHandler>(),
            "forward.updated" => _serviceProvider.GetService<ForwardUpdatedEventHandler>(),
            _ => null
        };
    }
}

Event Handler Interface:

public interface IWebhookEventHandler
{
    Task HandleAsync(CargoWiseWebhookPayload payload);
}

public class ShipmentCreatedEventHandler : IWebhookEventHandler
{
    private readonly ILogger<ShipmentCreatedEventHandler> _logger;
    private readonly IShipmentService _shipmentService;
    private readonly INotificationService _notificationService;
    
    public ShipmentCreatedEventHandler(
        ILogger<ShipmentCreatedEventHandler> logger,
        IShipmentService shipmentService,
        INotificationService notificationService)
    {
        _logger = logger;
        _shipmentService = shipmentService;
        _notificationService = notificationService;
    }
    
    public async Task HandleAsync(CargoWiseWebhookPayload payload)
    {
        try
        {
            var shipmentData = payload.Data.ToObject<ShipmentCreatedData>();
            
            // Process shipment creation
            await _shipmentService.ProcessShipmentCreatedAsync(shipmentData);
            
            // Send notifications
            await _notificationService.NotifyShipmentCreatedAsync(shipmentData);
            
            _logger.LogInformation("Shipment created event processed: {ShipmentId}", shipmentData.ShipmentId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing shipment created event");
            throw;
        }
    }
}

Idempotency Handling

C# Idempotency Implementation:

public class CargoWiseWebhookIdempotencyService
{
    private readonly IMemoryCache _cache;
    private readonly ILogger<CargoWiseWebhookIdempotencyService> _logger;
    
    public CargoWiseWebhookIdempotencyService(IMemoryCache cache, ILogger<CargoWiseWebhookIdempotencyService> logger)
    {
        _cache = cache;
        _logger = logger;
    }
    
    public async Task<bool> IsEventAlreadyProcessed(string eventId)
    {
        var key = $"webhook_event_{eventId}";
        
        if (_cache.TryGetValue(key, out _))
        {
            _logger.LogInformation("Event already processed: {EventId}", eventId);
            return true;
        }
        
        return false;
    }
    
    public async Task MarkEventAsProcessed(string eventId)
    {
        var key = $"webhook_event_{eventId}";
        var options = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24)
        };
        
        _cache.Set(key, true, options);
        _logger.LogDebug("Event marked as processed: {EventId}", eventId);
    }
    
    public async Task<bool> ProcessEventIfNotProcessed(string eventId, Func<Task> processAction)
    {
        if (await IsEventAlreadyProcessed(eventId))
        {
            return false;
        }
        
        await processAction();
        await MarkEventAsProcessed(eventId);
        
        return true;
    }
}

Error Handling and Retry Logic

Webhook Error Handling

C# Error Handling:

public class CargoWiseWebhookErrorHandler
{
    private readonly ILogger<CargoWiseWebhookErrorHandler> _logger;
    private readonly IWebhookRetryService _retryService;
    
    public CargoWiseWebhookErrorHandler(ILogger<CargoWiseWebhookErrorHandler> logger, IWebhookRetryService retryService)
    {
        _logger = logger;
        _retryService = retryService;
    }
    
    public async Task<WebhookProcessingResult> HandleWebhookWithErrorHandling(CargoWiseWebhookPayload payload)
    {
        try
        {
            // Process webhook
            await ProcessWebhook(payload);
            
            return WebhookProcessingResult.Success();
        }
        catch (ValidationException ex)
        {
            _logger.LogError(ex, "Validation error processing webhook: {EventType}", payload.EventType);
            return WebhookProcessingResult.ValidationError(ex.Message);
        }
        catch (BusinessLogicException ex)
        {
            _logger.LogError(ex, "Business logic error processing webhook: {EventType}", payload.EventType);
            return WebhookProcessingResult.BusinessError(ex.Message);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unexpected error processing webhook: {EventType}", payload.EventType);
            
            // Queue for retry
            await _retryService.QueueForRetry(payload, ex);
            
            return WebhookProcessingResult.SystemError(ex.Message);
        }
    }
    
    private async Task ProcessWebhook(CargoWiseWebhookPayload payload)
    {
        // Implementation here
    }
}

Retry Logic Implementation

C# Retry Service:

public class CargoWiseWebhookRetryService
{
    private readonly ILogger<CargoWiseWebhookRetryService> _logger;
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly IConfiguration _configuration;
    
    public CargoWiseWebhookRetryService(
        ILogger<CargoWiseWebhookRetryService> logger,
        IBackgroundTaskQueue taskQueue,
        IConfiguration configuration)
    {
        _logger = logger;
        _taskQueue = taskQueue;
        _configuration = configuration;
    }
    
    public async Task QueueForRetry(CargoWiseWebhookPayload payload, Exception exception)
    {
        var retryCount = GetRetryCount(payload.EventId);
        var maxRetries = _configuration.GetValue<int>("CargoWise:Webhook:MaxRetries", 3);
        
        if (retryCount >= maxRetries)
        {
            _logger.LogError("Max retries exceeded for webhook event: {EventId}", payload.EventId);
            await HandleMaxRetriesExceeded(payload, exception);
            return;
        }
        
        var retryDelay = CalculateRetryDelay(retryCount);
        var retryTask = new WebhookRetryTask
        {
            Payload = payload,
            RetryCount = retryCount + 1,
            RetryAfter = DateTime.UtcNow.Add(retryDelay),
            OriginalException = exception
        };
        
        await _taskQueue.QueueBackgroundWorkItemAsync(async token =>
        {
            await Task.Delay(retryDelay, token);
            await ProcessRetryTask(retryTask);
        });
        
        _logger.LogInformation("Webhook queued for retry: {EventId}, Retry {RetryCount}/{MaxRetries}", 
            payload.EventId, retryCount + 1, maxRetries);
    }
    
    private TimeSpan CalculateRetryDelay(int retryCount)
    {
        // Exponential backoff with jitter
        var baseDelay = TimeSpan.FromSeconds(Math.Pow(2, retryCount));
        var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(0, 1000));
        return baseDelay.Add(jitter);
    }
}

Testing Webhook Integrations

Local Development Setup

C# Webhook Testing:

[TestClass]
public class CargoWiseWebhookTests
{
    private WebApplicationFactory<Program> _factory;
    private HttpClient _client;
    
    [TestInitialize]
    public void Setup()
    {
        _factory = new WebApplicationFactory<Program>();
        _client = _factory.CreateClient();
    }
    
    [TestMethod]
    public async Task HandleWebhook_ValidPayload_ReturnsOk()
    {
        // Arrange
        var payload = new CargoWiseWebhookPayload
        {
            EventType = "shipment.created",
            EventId = "evt_test_001",
            Timestamp = DateTime.UtcNow,
            Data = new
            {
                ShipmentId = "SH-TEST-001",
                Mode = "Air",
                Status = "Created"
            }
        };
        
        var json = JsonSerializer.Serialize(payload);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        
        // Add valid signature
        var signature = CalculateTestSignature(json);
        _client.DefaultRequestHeaders.Add("X-CargoWise-Signature", signature);
        
        // Act
        var response = await _client.PostAsync("/api/cargowise/webhook", content);
        
        // Assert
        Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
    }
    
    [TestMethod]
    public async Task HandleWebhook_InvalidSignature_ReturnsUnauthorized()
    {
        // Arrange
        var payload = new CargoWiseWebhookPayload
        {
            EventType = "shipment.created",
            EventId = "evt_test_002",
            Timestamp = DateTime.UtcNow,
            Data = new { ShipmentId = "SH-TEST-002" }
        };
        
        var json = JsonSerializer.Serialize(payload);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        
        // Add invalid signature
        _client.DefaultRequestHeaders.Add("X-CargoWise-Signature", "invalid_signature");
        
        // Act
        var response = await _client.PostAsync("/api/cargowise/webhook", content);
        
        // Assert
        Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
    }
}

Python Webhook Testing:

import pytest
import json
import hmac
import hashlib
from unittest.mock import Mock, patch

class TestCargoWiseWebhook:
    def setup_method(self):
        self.app = create_test_app()
        self.client = self.app.test_client()
        self.webhook_secret = "test_secret"
    
    def test_handle_webhook_valid_payload(self):
        """Test webhook with valid payload"""
        payload = {
            "event_type": "shipment.created",
            "event_id": "evt_test_001",
            "timestamp": "2025-01-19T10:00:00Z",
            "data": {
                "shipment_id": "SH-TEST-001",
                "mode": "Air",
                "status": "Created"
            }
        }
        
        # Calculate signature
        signature = self.calculate_signature(json.dumps(payload))
        
        response = self.client.post(
            '/api/cargowise/webhook',
            json=payload,
            headers={'X-CargoWise-Signature': signature}
        )
        
        assert response.status_code == 200
    
    def test_handle_webhook_invalid_signature(self):
        """Test webhook with invalid signature"""
        payload = {
            "event_type": "shipment.created",
            "event_id": "evt_test_002",
            "timestamp": "2025-01-19T10:00:00Z",
            "data": {"shipment_id": "SH-TEST-002"}
        }
        
        response = self.client.post(
            '/api/cargowise/webhook',
            json=payload,
            headers={'X-CargoWise-Signature': 'invalid_signature'}
        )
        
        assert response.status_code == 401
    
    def calculate_signature(self, payload: str) -> str:
        """Calculate webhook signature for testing"""
        return hmac.new(
            self.webhook_secret.encode('utf-8'),
            payload.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()

Webhook Testing Tools

1. ngrok for Local Testing:

# Install ngrok
npm install -g ngrok

# Start your local application
dotnet run

# In another terminal, expose your local app
ngrok http 5000

# Use the ngrok URL in CargoWise webhook configuration
# Example: https://abc123.ngrok.io/api/cargowise/webhook

2. Webhook Testing with Postman:

{
  "event_type": "shipment.created",
  "event_id": "evt_postman_test_001",
  "timestamp": "2025-01-19T10:00:00Z",
  "data": {
    "shipment_id": "SH-POSTMAN-001",
    "mode": "Air",
    "origin": {
      "city": "New York",
      "country": "US",
      "airport": "JFK"
    },
    "destination": {
      "city": "London",
      "country": "GB",
      "airport": "LHR"
    },
    "cargo": {
      "description": "Test Cargo",
      "weight": 100.0,
      "volume": 1.0,
      "pieces": 1
    },
    "status": "Created",
    "created_by": "test@example.com",
    "created_at": "2025-01-19T10:00:00Z"
  }
}

Production Deployment Considerations

Monitoring and Logging

C# Webhook Monitoring:

public class CargoWiseWebhookMonitoringService
{
    private readonly ILogger<CargoWiseWebhookMonitoringService> _logger;
    private readonly IMetricsCollector _metricsCollector;
    
    public CargoWiseWebhookMonitoringService(ILogger<CargoWiseWebhookMonitoringService> logger, IMetricsCollector metricsCollector)
    {
        _logger = logger;
        _metricsCollector = metricsCollector;
    }
    
    public void RecordWebhookReceived(string eventType, string eventId)
    {
        _metricsCollector.IncrementCounter("cargowise_webhook_received_total", 
            new Dictionary<string, string> { ["event_type"] = eventType });
        
        _logger.LogInformation("Webhook received: {EventType} - {EventId}", eventType, eventId);
    }
    
    public void RecordWebhookProcessed(string eventType, string eventId, TimeSpan processingTime)
    {
        _metricsCollector.IncrementCounter("cargowise_webhook_processed_total", 
            new Dictionary<string, string> { ["event_type"] = eventType });
        
        _metricsCollector.RecordHistogram("cargowise_webhook_processing_duration_seconds", 
            processingTime.TotalSeconds, 
            new Dictionary<string, string> { ["event_type"] = eventType });
        
        _logger.LogInformation("Webhook processed: {EventType} - {EventId} in {ProcessingTime}ms", 
            eventType, eventId, processingTime.TotalMilliseconds);
    }
    
    public void RecordWebhookError(string eventType, string eventId, Exception exception)
    {
        _metricsCollector.IncrementCounter("cargowise_webhook_errors_total", 
            new Dictionary<string, string> { 
                ["event_type"] = eventType,
                ["error_type"] = exception.GetType().Name
            });
        
        _logger.LogError(exception, "Webhook processing error: {EventType} - {EventId}", eventType, eventId);
    }
}

Health Checks

C# Webhook Health Check:

public class CargoWiseWebhookHealthCheck : IHealthCheck
{
    private readonly ILogger<CargoWiseWebhookHealthCheck> _logger;
    private readonly IWebhookProcessor _webhookProcessor;
    
    public CargoWiseWebhookHealthCheck(ILogger<CargoWiseWebhookHealthCheck> logger, IWebhookProcessor webhookProcessor)
    {
        _logger = logger;
        _webhookProcessor = webhookProcessor;
    }
    
    public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        try
        {
            // Test webhook processing with a test payload
            var testPayload = CreateTestPayload();
            await _webhookProcessor.ProcessWebhookAsync(testPayload);
            
            return HealthCheckResult.Healthy("Webhook processing is working");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Webhook health check failed");
            return HealthCheckResult.Unhealthy($"Webhook processing failed: {ex.Message}");
        }
    }
    
    private CargoWiseWebhookPayload CreateTestPayload()
    {
        return new CargoWiseWebhookPayload
        {
            EventType = "health.check",
            EventId = "health_check_" + Guid.NewGuid().ToString("N")[..8],
            Timestamp = DateTime.UtcNow,
            Data = new { test = true }
        };
    }
}

Conclusion

CargoWise webhook integrations provide a powerful way to build real-time, responsive applications that can react instantly to changes in your freight forwarding operations. By following the patterns and best practices outlined in this guide, you can create robust, secure, and scalable webhook integrations.

Key Takeaways:

  1. Security First: Always implement signature verification and IP whitelisting
  2. Idempotency: Handle duplicate events gracefully
  3. Error Handling: Implement comprehensive error handling and retry logic
  4. Monitoring: Use proper logging and metrics for operational visibility
  5. Testing: Test thoroughly with both unit tests and integration tests

Next Steps:

  1. Set up webhook endpoint with proper security measures
  2. Implement event handlers for the events you need to process
  3. Add monitoring and logging for production visibility
  4. Test thoroughly with CargoWise sandbox environment
  5. Deploy to production with proper health checks and monitoring

For more CargoWise integration guidance and implementation support, explore our CargoWise Integration Services or contact our team for personalized consulting.

FAQ

Q: How do I handle webhook failures and retries? A: Implement exponential backoff with jitter, use message queues for retry processing, and set maximum retry limits. CargoWise will also retry failed webhooks automatically.

Q: What security measures should I implement for webhooks? A: Always implement signature verification using HMAC-SHA256, use HTTPS endpoints, implement IP whitelisting, and add rate limiting to prevent abuse.

Q: How can I test webhooks during development? A: Use tools like ngrok to expose your local development server, implement webhook testing endpoints, and use CargoWise sandbox environment for testing.

Q: What happens if my webhook endpoint is down? A: CargoWise will retry failed webhooks according to the retry policy. Implement proper error handling and monitoring to ensure your endpoint is available.

Q: Can I process webhooks asynchronously? A: Yes, it's recommended to process webhooks asynchronously using background tasks or message queues to ensure fast response times and better error handling.

Related posts