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