Skip to main content

Webhook Integration Guide

Learn how to integrate Fuuffy webhooks to receive real-time order updates.

Overview​

Webhooks allow you to receive automatic notifications when order events occur, eliminating the need to poll the API for updates.

Benefits​

  • Real-time updates - Get notified instantly when events occur
  • Reduced API calls - No need to poll for status changes
  • Better user experience - Update customers immediately
  • Automated workflows - Trigger actions based on events

Setup Process​

1. Create a Webhook Endpoint​

Create a PHP script to receive webhook notifications:

<?php
// webhook-handler.php

// Log incoming requests for debugging
error_log("Webhook received: " . file_get_contents('php://input'));

// Get the raw POST data
$payload = file_get_contents('php://input');
$event = json_decode($payload, true);

// Verify the webhook signature
$signature = $_SERVER['HTTP_X_FUUFFY_SIGNATURE'] ?? '';
$secret = getenv('FUUFFY_WEBHOOK_SECRET');

if (!verifySignature($payload, $signature, $secret)) {
http_response_code(401);
exit(json_encode(['error' => 'Invalid signature']));
}

// Process the event
processEvent($event);

// Always return 200 OK quickly
http_response_code(200);
echo json_encode(['received' => true]);

function verifySignature($payload, $signature, $secret) {
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}

function processEvent($event) {
// Queue the event for async processing
// or process it directly if it's quick

switch ($event['event']) {
case 'order.delivered':
handleDelivered($event['data']);
break;
case 'order.in_transit':
handleInTransit($event['data']);
break;
case 'order.exception':
handleException($event['data']);
break;
default:
error_log("Unknown event: {$event['event']}");
}
}

function handleDelivered($data) {
// Update database
// Send customer notification
// Trigger post-delivery workflow
error_log("Order {$data['tracking_number']} delivered");
}

function handleInTransit($data) {
// Update tracking status
error_log("Order {$data['tracking_number']} in transit");
}

function handleException($data) {
// Alert customer service team
// Send notification to customer
error_log("Exception for {$data['tracking_number']}");
}
?>

2. Make Your Endpoint Publicly Accessible​

Your webhook endpoint must be:

  • Publicly accessible via HTTPS
  • Fast - Respond within 5 seconds
  • Reliable - Return 200 OK status

For local development, use ngrok:

ngrok http 8000

3. Register Your Webhook​

Contact support@fuuffy.com with:

  • Your webhook URL
  • Events you want to subscribe to
  • Your webhook secret (for signature verification)

Event Types​

EventDescriptionWhen It Fires
order.createdOrder createdWhen a new order is created
order.picked_upPackage picked upWhen carrier picks up the package
order.in_transitIn transitWhen package moves between facilities
order.out_for_deliveryOut for deliveryWhen package is out for delivery
order.deliveredDeliveredWhen package is delivered
order.failed_deliveryDelivery failedWhen delivery attempt fails
order.exceptionException occurredWhen an exception occurs
order.cancelledCancelledWhen order is cancelled

Webhook Payload Structure​

{
"event": "order.delivered",
"timestamp": "2024-01-18T15:30:00Z",
"data": {
"order_id": "order_1a2b3c4d5e6f",
"tracking_number": "FUF123456789",
"status": "delivered",
"location": {
"city": "Los Angeles",
"state": "CA",
"country": "US"
},
"description": "Package delivered to recipient",
"delivered_to": "Jane Smith",
"signature_required": false,
"proof_of_delivery_url": "${API_URL}/v1/orders/order_1a2b3c4d5e6f/pod"
}
}

Security Best Practices​

1. Verify Signatures​

Always verify the X-Fuuffy-Signature header:

<?php
function verifyWebhookSignature($payload, $signature, $secret) {
$expected_signature = hash_hmac('sha256', $payload, $secret);

if (!hash_equals($expected_signature, $signature)) {
error_log("Invalid webhook signature");
return false;
}

return true;
}
?>

2. Use HTTPS Only​

Never use HTTP for webhook endpoints. HTTPS is required.

3. Validate Event Data​

Always validate the event data before processing:

<?php
function validateEvent($event) {
if (!isset($event['event']) || !isset($event['data'])) {
return false;
}

if (!isset($event['data']['order_id'])) {
return false;
}

return true;
}
?>

4. Handle Duplicates​

Events may be sent multiple times. Use idempotency:

<?php
function processEventIdempotent($event) {
$event_id = $event['data']['order_id'] . '_' . $event['timestamp'];

// Check if already processed
if (isEventProcessed($event_id)) {
error_log("Event already processed: {$event_id}");
return;
}

// Process event
processEvent($event);

// Mark as processed
markEventProcessed($event_id);
}
?>

Testing Webhooks​

Local Testing with ngrok​

# Start your local server
php -S localhost:8000

# In another terminal, start ngrok
ngrok http 8000

# Use the ngrok URL for testing
# Example: https://abc123.ngrok.io/webhook-handler.php

Manual Testing​

Send a test webhook using cURL:

curl -X POST http://localhost:8000/webhook-handler.php \
-H "Content-Type: application/json" \
-H "X-Fuuffy-Signature: test_signature" \
-d '{
"event": "order.delivered",
"timestamp": "2024-01-18T15:30:00Z",
"data": {
"order_id": "order_test",
"tracking_number": "TEST123",
"status": "delivered"
}
}'

Troubleshooting​

Webhook Not Receiving Events​

  1. Check that your endpoint is publicly accessible
  2. Verify HTTPS is configured correctly
  3. Check firewall settings
  4. Review server logs for errors

Signature Verification Failing​

  1. Ensure you're using the correct webhook secret
  2. Verify you're hashing the raw request body
  3. Check for whitespace or encoding issues

Timeouts​

  1. Return 200 OK immediately
  2. Process events asynchronously
  3. Optimize database queries
  4. Use queues for heavy processing

Next Steps​