📖 Looking for karrio's legacy docs? Visit docs.karrio.io

Community Edition
Core Feature

Webhooks

Every Karrio project comes with comprehensive webhook capabilities, providing real-time event notifications for all shipping activities with automatic retry mechanisms, signature verification, and event filtering for building responsive applications.

Features

Real-Time Event Notifications

You don’t have to poll APIs for status updates. Our webhook system instantly pushes notifications to your application the moment shipping events occur, enabling immediate responses and real-time user experiences.

Webhook Management Dashboard

Screenshot: Webhook configuration interface with event filters and delivery status

Automatic Retry Mechanism

Failed webhook deliveries are automatically retried with exponential backoff to ensure reliable event delivery even during temporary outages.

Signature Verification

All webhook payloads include cryptographic signatures to verify authenticity and prevent unauthorized access to your endpoints.

Event Filtering

Subscribe only to the events you need, reducing unnecessary traffic and processing overhead in your application.

Delivery Monitoring

Track webhook delivery success rates, response times, and failure patterns with detailed logging and analytics.

Additional features

  • Karrio extends webhooks with custom retry policies and delivery scheduling.
  • Every webhook includes structured event data and comprehensive metadata.
  • Karrio manages webhook endpoint health monitoring and automatic failover.
  • Support for multiple webhook endpoints with different event configurations.

Data Flow

Webhook Architecture

API Reference

REST API

Create Webhook

1curl -X POST "https://api.karrio.io/v1/webhooks" \ 2 -H "Authorization: Token YOUR_API_KEY" \ 3 -H "Content-Type: application/json" \ 4 -d '{ 5 "url": "https://your-app.com/webhooks/karrio", 6 "description": "Main webhook endpoint", 7 "enabled_events": [ 8 "shipment.created", 9 "shipment.purchased", 10 "tracking.status_updated", 11 "tracking.delivered", 12 "order.fulfilled" 13 ], 14 "secret": "your-webhook-secret" 15 }'

Response:

1{ 2 "id": "wh_1234567890", 3 "url": "https://your-app.com/webhooks/karrio", 4 "description": "Main webhook endpoint", 5 "enabled_events": [ 6 "shipment.created", 7 "shipment.purchased", 8 "tracking.status_updated", 9 "tracking.delivered", 10 "order.fulfilled" 11 ], 12 "secret": "your-webhook-secret", 13 "created_at": "2024-01-15T10:30:00Z", 14 "is_active": true, 15 "last_response_status": null, 16 "total_deliveries": 0, 17 "successful_deliveries": 0 18}

List Webhooks

1curl -X GET "https://api.karrio.io/v1/webhooks" \ 2 -H "Authorization: Token YOUR_API_KEY"

Response:

1{ 2 "count": 2, 3 "next": null, 4 "previous": null, 5 "results": [ 6 { 7 "id": "wh_1234567890", 8 "url": "https://your-app.com/webhooks/karrio", 9 "description": "Main webhook endpoint", 10 "enabled_events": ["shipment.created", "tracking.status_updated"], 11 "is_active": true, 12 "created_at": "2024-01-15T10:30:00Z", 13 "last_response_status": 200, 14 "total_deliveries": 150, 15 "successful_deliveries": 148 16 }, 17 { 18 "id": "wh_1234567891", 19 "url": "https://backup.your-app.com/webhooks", 20 "description": "Backup webhook endpoint", 21 "enabled_events": ["order.fulfilled"], 22 "is_active": true, 23 "created_at": "2024-01-16T09:00:00Z", 24 "last_response_status": 200, 25 "total_deliveries": 45, 26 "successful_deliveries": 45 27 } 28 ] 29}

Test Webhook

1curl -X POST "https://api.karrio.io/v1/webhooks/wh_1234567890/test" \ 2 -H "Authorization: Token YOUR_API_KEY" \ 3 -H "Content-Type: application/json" \ 4 -d '{ 5 "event_type": "shipment.created" 6 }'

Response:

1{ 2 "success": true, 3 "delivery_id": "del_1234567890", 4 "response_status": 200, 5 "response_body": "OK", 6 "response_time": 245, 7 "delivered_at": "2024-01-15T10:35:00Z" 8}

Webhook Events

Shipment Created Event

1{ 2 "id": "evt_1234567890", 3 "type": "shipment.created", 4 "data": { 5 "id": "shp_1234567890", 6 "tracking_number": "1Z999AA1234567890", 7 "carrier_name": "ups", 8 "carrier_id": "ups_connection", 9 "service": "ups_ground", 10 "status": "created", 11 "shipper": { 12 "person_name": "Acme Corp", 13 "address_line1": "123 Business Ave", 14 "city": "Chicago", 15 "state_code": "IL", 16 "postal_code": "60601", 17 "country_code": "US" 18 }, 19 "recipient": { 20 "person_name": "John Doe", 21 "address_line1": "456 Customer St", 22 "city": "New York", 23 "state_code": "NY", 24 "postal_code": "10001", 25 "country_code": "US" 26 }, 27 "parcels": [ 28 { 29 "weight": 2.5, 30 "weight_unit": "LB", 31 "length": 12, 32 "width": 8, 33 "height": 6, 34 "dimension_unit": "IN" 35 } 36 ], 37 "selected_rate": { 38 "id": "rate_1234567890", 39 "total_charge": 15.25, 40 "currency": "USD", 41 "transit_days": 3 42 }, 43 "reference": "ORDER-2024-001", 44 "metadata": { 45 "order_id": "ord_1234567890", 46 "customer_id": "cust_1234567890" 47 }, 48 "created_at": "2024-01-15T10:30:00Z" 49 }, 50 "created_at": "2024-01-15T10:30:00Z", 51 "api_version": "2024-01-01" 52}

Tracking Status Updated Event

1{ 2 "id": "evt_1234567891", 3 "type": "tracking.status_updated", 4 "data": { 5 "id": "trk_1234567890", 6 "tracking_number": "1Z999AA1234567890", 7 "carrier_name": "ups", 8 "status": "in_transit", 9 "previous_status": "shipped", 10 "estimated_delivery": "2024-01-18T17:00:00Z", 11 "events": [ 12 { 13 "code": "IT", 14 "description": "In Transit", 15 "location": "Chicago, IL, US", 16 "timestamp": "2024-01-16T08:30:00Z" 17 }, 18 { 19 "code": "DP", 20 "description": "Departed Facility", 21 "location": "Origin Facility", 22 "timestamp": "2024-01-15T22:15:00Z" 23 } 24 ], 25 "metadata": { 26 "shipment_id": "shp_1234567890", 27 "order_id": "ord_1234567890" 28 }, 29 "updated_at": "2024-01-16T08:30:00Z" 30 }, 31 "created_at": "2024-01-16T08:30:00Z", 32 "api_version": "2024-01-01" 33}

Order Fulfilled Event

1{ 2 "id": "evt_1234567892", 3 "type": "order.fulfilled", 4 "data": { 5 "id": "ord_1234567890", 6 "order_number": "ORDER-2024-001", 7 "status": "fulfilled", 8 "customer": { 9 "id": "cust_1234567890", 10 "email": "customer@example.com", 11 "name": "John Doe" 12 }, 13 "shipments": [ 14 { 15 "id": "shp_1234567890", 16 "tracking_number": "1Z999AA1234567890", 17 "carrier_name": "ups", 18 "service": "ups_ground" 19 } 20 ], 21 "line_items": [ 22 { 23 "id": "item_1234567890", 24 "sku": "PRODUCT-001", 25 "quantity": 2, 26 "fulfilled_quantity": 2 27 } 28 ], 29 "fulfilled_at": "2024-01-15T10:30:00Z", 30 "metadata": { 31 "source": "website", 32 "sales_channel": "online" 33 } 34 }, 35 "created_at": "2024-01-15T10:30:00Z", 36 "api_version": "2024-01-01" 37}

Event Handling

Basic Webhook Handler

1const express = require("express"); 2const crypto = require("crypto"); 3const app = express(); 4 5app.use(express.json()); 6 7app.post("/webhooks/karrio", (req, res) => { 8 const event = req.body; 9 const signature = req.headers["x-karrio-signature"]; 10 11 // Verify webhook signature 12 if (!verifySignature(req.body, signature)) { 13 return res.status(401).send("Unauthorized"); 14 } 15 16 // Process the event 17 try { 18 handleWebhookEvent(event); 19 res.status(200).send("OK"); 20 } catch (error) { 21 console.error("Webhook processing error:", error); 22 res.status(500).send("Internal Server Error"); 23 } 24}); 25 26function verifySignature(payload, signature) { 27 const secret = process.env.WEBHOOK_SECRET; 28 const expectedSignature = crypto 29 .createHmac("sha256", secret) 30 .update(JSON.stringify(payload)) 31 .digest("hex"); 32 33 return signature === `sha256=${expectedSignature}`; 34} 35 36function handleWebhookEvent(event) { 37 console.log(`Received ${event.type} event:`, event.id); 38 39 switch (event.type) { 40 case "shipment.created": 41 handleShipmentCreated(event.data); 42 break; 43 44 case "shipment.purchased": 45 handleShipmentPurchased(event.data); 46 break; 47 48 case "tracking.status_updated": 49 handleTrackingUpdate(event.data); 50 break; 51 52 case "tracking.delivered": 53 handlePackageDelivered(event.data); 54 break; 55 56 case "order.fulfilled": 57 handleOrderFulfilled(event.data); 58 break; 59 60 default: 61 console.log(`Unhandled event type: ${event.type}`); 62 } 63}

Shipment Event Handlers

1async function handleShipmentCreated(shipment) { 2 console.log( 3 `Shipment ${shipment.id} created for order ${shipment.reference}`, 4 ); 5 6 // Update order status in database 7 await updateOrderStatus(shipment.reference, "processing"); 8 9 // Create internal tracking record 10 await createTrackingRecord({ 11 orderId: shipment.metadata.order_id, 12 shipmentId: shipment.id, 13 trackingNumber: shipment.tracking_number, 14 carrier: shipment.carrier_name, 15 }); 16} 17 18async function handleShipmentPurchased(shipment) { 19 console.log(`Label purchased for shipment ${shipment.tracking_number}`); 20 21 // Send shipping confirmation to customer 22 await sendShippingConfirmation({ 23 orderId: shipment.metadata.order_id, 24 trackingNumber: shipment.tracking_number, 25 carrier: shipment.carrier_name, 26 estimatedDelivery: shipment.estimated_delivery, 27 }); 28 29 // Update order status 30 await updateOrderStatus(shipment.reference, "shipped"); 31} 32 33async function sendShippingConfirmation(shipmentInfo) { 34 const order = await getOrderById(shipmentInfo.orderId); 35 36 await sendEmail({ 37 to: order.customer.email, 38 subject: `Your order ${order.orderNumber} has shipped!`, 39 template: "shipping_confirmation", 40 data: { 41 customerName: order.customer.name, 42 orderNumber: order.orderNumber, 43 trackingNumber: shipmentInfo.trackingNumber, 44 carrier: shipmentInfo.carrier, 45 trackingUrl: `https://your-app.com/track/${shipmentInfo.trackingNumber}`, 46 }, 47 }); 48}

Tracking Event Handlers

1async function handleTrackingUpdate(tracking) { 2 console.log( 3 `Tracking update: ${tracking.tracking_number} - ${tracking.status}`, 4 ); 5 6 // Update tracking status in database 7 await updateTrackingStatus(tracking.tracking_number, { 8 status: tracking.status, 9 location: tracking.events[0]?.location, 10 timestamp: tracking.events[0]?.timestamp, 11 estimatedDelivery: tracking.estimated_delivery, 12 }); 13 14 // Send customer notification for key status updates 15 const notificationStatuses = [ 16 "shipped", 17 "in_transit", 18 "out_for_delivery", 19 "delivered", 20 ]; 21 22 if (notificationStatuses.includes(tracking.status)) { 23 await sendTrackingNotification(tracking); 24 } 25 26 // Handle delivery exceptions 27 if ( 28 tracking.status === "delivery_failed" || 29 tracking.status === "exception" 30 ) { 31 await handleDeliveryException(tracking); 32 } 33} 34 35async function handlePackageDelivered(tracking) { 36 console.log(`Package delivered: ${tracking.tracking_number}`); 37 38 // Update order status to completed 39 const shipment = await getShipmentByTrackingNumber(tracking.tracking_number); 40 if (shipment?.metadata?.order_id) { 41 await updateOrderStatus(shipment.reference, "completed"); 42 } 43 44 // Send delivery confirmation 45 await sendDeliveryConfirmation(tracking); 46 47 // Trigger post-delivery workflows 48 await triggerPostDeliveryWorkflows(tracking); 49} 50 51async function sendTrackingNotification(tracking) { 52 const shipment = await getShipmentByTrackingNumber(tracking.tracking_number); 53 const order = await getOrderById(shipment.metadata.order_id); 54 55 const statusMessages = { 56 shipped: "Your package has been picked up and is on its way!", 57 in_transit: "Your package is in transit", 58 out_for_delivery: "Your package is out for delivery today!", 59 delivered: "Your package has been delivered!", 60 }; 61 62 await sendEmail({ 63 to: order.customer.email, 64 subject: `Package Update: ${order.orderNumber}`, 65 template: "tracking_update", 66 data: { 67 customerName: order.customer.name, 68 orderNumber: order.orderNumber, 69 trackingNumber: tracking.tracking_number, 70 status: tracking.status, 71 statusMessage: statusMessages[tracking.status], 72 location: tracking.events[0]?.location, 73 estimatedDelivery: tracking.estimated_delivery, 74 }, 75 }); 76}

Order Event Handlers

1async function handleOrderFulfilled(order) { 2 console.log( 3 `Order ${order.order_number} fulfilled with ${order.shipments.length} shipments`, 4 ); 5 6 // Update order fulfillment status 7 await updateOrderFulfillment(order.id, { 8 status: "fulfilled", 9 fulfilledAt: order.fulfilled_at, 10 shipments: order.shipments, 11 }); 12 13 // Send fulfillment notification 14 await sendFulfillmentNotification(order); 15 16 // Update inventory 17 for (const item of order.line_items) { 18 await updateInventory(item.sku, -item.fulfilled_quantity); 19 } 20 21 // Trigger post-fulfillment analytics 22 await trackFulfillmentMetrics(order); 23} 24 25async function sendFulfillmentNotification(order) { 26 const trackingNumbers = order.shipments.map((s) => s.tracking_number); 27 28 await sendEmail({ 29 to: order.customer.email, 30 subject: `Order ${order.order_number} Fulfilled`, 31 template: "order_fulfilled", 32 data: { 33 customerName: order.customer.name, 34 orderNumber: order.order_number, 35 items: order.line_items, 36 trackingNumbers: trackingNumbers, 37 trackingUrl: `https://your-app.com/orders/${order.id}/tracking`, 38 }, 39 }); 40}

Error Handling & Reliability

Retry Configuration

1class WebhookProcessor { 2 constructor() { 3 this.retryConfig = { 4 maxAttempts: 5, 5 initialDelay: 1000, // 1 second 6 maxDelay: 300000, // 5 minutes 7 backoffMultiplier: 2, 8 }; 9 } 10 11 async processWebhook(webhook, event) { 12 let attempt = 1; 13 let delay = this.retryConfig.initialDelay; 14 15 while (attempt <= this.retryConfig.maxAttempts) { 16 try { 17 const response = await this.deliverWebhook(webhook, event); 18 19 if (response.status >= 200 && response.status < 300) { 20 await this.logDeliverySuccess(webhook, event, response); 21 return { success: true, attempts: attempt }; 22 } 23 24 throw new Error(`HTTP ${response.status}: ${response.statusText}`); 25 } catch (error) { 26 console.error( 27 `Webhook delivery attempt ${attempt} failed:`, 28 error.message, 29 ); 30 31 if (attempt === this.retryConfig.maxAttempts) { 32 await this.logDeliveryFailure(webhook, event, error); 33 await this.sendToDeadLetterQueue(webhook, event, error); 34 return { success: false, attempts: attempt, error: error.message }; 35 } 36 37 // Wait before retry 38 await this.sleep(delay); 39 delay = Math.min( 40 delay * this.retryConfig.backoffMultiplier, 41 this.retryConfig.maxDelay, 42 ); 43 attempt++; 44 } 45 } 46 } 47 48 async deliverWebhook(webhook, event) { 49 const signature = this.generateSignature(event, webhook.secret); 50 51 return await fetch(webhook.url, { 52 method: "POST", 53 headers: { 54 "Content-Type": "application/json", 55 "X-Karrio-Signature": signature, 56 "X-Karrio-Event-Type": event.type, 57 "X-Karrio-Delivery-Id": event.id, 58 }, 59 body: JSON.stringify(event), 60 timeout: 30000, // 30 seconds 61 }); 62 } 63 64 generateSignature(event, secret) { 65 const payload = JSON.stringify(event); 66 const signature = crypto 67 .createHmac("sha256", secret) 68 .update(payload) 69 .digest("hex"); 70 71 return `sha256=${signature}`; 72 } 73 74 sleep(ms) { 75 return new Promise((resolve) => setTimeout(resolve, ms)); 76 } 77}

Dead Letter Queue Handling

1class DeadLetterQueueHandler { 2 async processFailedWebhooks() { 3 const failedWebhooks = await this.getFailedWebhooks(); 4 5 for (const failedWebhook of failedWebhooks) { 6 // Analyze failure reason 7 const failureReason = this.analyzeFailure(failedWebhook); 8 9 switch (failureReason) { 10 case "endpoint_down": 11 // Check if endpoint is back online 12 if (await this.isEndpointHealthy(failedWebhook.webhook.url)) { 13 await this.retryWebhook(failedWebhook); 14 } 15 break; 16 17 case "invalid_endpoint": 18 // Disable webhook and notify admin 19 await this.disableWebhook(failedWebhook.webhook.id); 20 await this.notifyAdmin(failedWebhook); 21 break; 22 23 case "authentication_error": 24 // Webhook secret might be wrong 25 await this.notifyWebhookOwner(failedWebhook); 26 break; 27 28 default: 29 // Manual review required 30 await this.flagForManualReview(failedWebhook); 31 } 32 } 33 } 34 35 analyzeFailure(failedWebhook) { 36 const lastError = failedWebhook.lastError; 37 38 if (lastError.includes("ECONNREFUSED") || lastError.includes("timeout")) { 39 return "endpoint_down"; 40 } 41 42 if (lastError.includes("404") || lastError.includes("DNS")) { 43 return "invalid_endpoint"; 44 } 45 46 if (lastError.includes("401") || lastError.includes("403")) { 47 return "authentication_error"; 48 } 49 50 return "unknown"; 51 } 52}

Use Cases

E-commerce Order Fulfillment

Perfect for online stores:

  • Order-to-Shipment Automation: Automatically update order status when shipments are created
  • Customer Communication: Send real-time shipping and delivery notifications
  • Inventory Management: Update stock levels when orders are fulfilled
  • Customer Service: Proactively handle delivery exceptions

Marketplace Integration

Designed for multi-vendor marketplaces:

  • Seller Notifications: Notify sellers when their orders are shipped
  • Performance Tracking: Monitor seller fulfillment performance
  • Customer Updates: Provide unified tracking experience across vendors
  • Analytics: Track marketplace shipping metrics

Enterprise Operations

Built for complex shipping workflows:

  • System Integration: Connect shipping events to ERP and CRM systems
  • Compliance Monitoring: Track shipping compliance and audit requirements
  • Cost Tracking: Monitor shipping costs and carrier performance
  • Exception Handling: Automate responses to delivery issues

Getting Started

Ready to implement real-time shipping notifications with Karrio webhooks? Follow these steps:

  1. Create webhook endpoints in your application to receive events
  2. Register webhooks with the events you need to monitor
  3. Implement signature verification for security
  4. Test webhook delivery and error handling

Next Steps

  • Learn about workflows for automated webhook-driven processes
  • Explore api logs for webhook delivery monitoring
  • Set up events to understand the full event system
  • Configure tracking for shipment monitoring events