Agent Skills: Flexport Webhooks & Events

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/flexport-webhooks-events

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/flexport-pack/skills/flexport-webhooks-events

Skill Files

Browse the full folder contents for flexport-webhooks-events.

Download Skill

Loading file tree…

plugins/saas-packs/flexport-pack/skills/flexport-webhooks-events/SKILL.md

Skill Metadata

Name
flexport-webhooks-events
Description
|

Flexport Webhooks & Events

Overview

Flexport sends webhook notifications for shipment milestones, booking confirmations, PO updates, invoice events, and document availability. Webhooks are configured in Portal > Settings with a secret token for HMAC-SHA256 signature verification via the X-Hub-Signature header.

Webhook Event Types

| Category | Events | Use Case | |----------|--------|----------| | Milestones | cargo_ready, departed, arrived, customs_cleared, delivered | Shipment tracking | | Transit | estimated_departure, estimated_arrival, actual_departure | ETA updates | | Bookings | booking_confirmed, booking_amended | Booking lifecycle | | Purchase Orders | po_created, po_updated, po_archived | PO management | | Invoices | invoice_created, freight_invoice_ready | Billing | | Documents | document_uploaded, bill_of_lading_ready | Document management | | Container | container_loaded, container_unloaded | Container tracking |

Instructions

Step 1: Create Webhook Endpoint in Flexport

Navigate to Portal > Settings > Webhooks > Add Endpoint:

  • URL: https://your-app.com/webhooks/flexport
  • Secret: Generate a strong random string
  • Events: Select event types to subscribe to

Step 2: Implement Webhook Handler

import crypto from 'crypto';
import express from 'express';

const app = express();

// IMPORTANT: Use raw body for signature verification
app.post('/webhooks/flexport', express.raw({ type: '*/*' }), async (req, res) => {
  // Step 1: Verify signature
  const signature = req.headers['x-hub-signature'] as string;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.FLEXPORT_WEBHOOK_SECRET!)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Invalid signature');
  }

  // Step 2: Parse and route event
  const event = JSON.parse(req.body.toString());
  console.log(`Webhook: ${event.type} | ID: ${event.data?.id}`);

  try {
    await routeEvent(event);
    res.status(200).send('OK');
  } catch (err) {
    console.error('Webhook processing failed:', err);
    res.status(500).send('Processing error');
    // Dead letter: store for retry
  }
});

// Step 3: Route events to handlers
async function routeEvent(event: { type: string; data: any }) {
  switch (event.type) {
    case 'shipment.milestone':
      await handleMilestone(event.data);
      break;
    case 'shipment.eta_updated':
      await handleETAUpdate(event.data);
      break;
    case 'booking.confirmed':
      await handleBookingConfirmed(event.data);
      break;
    case 'invoice.created':
      await handleInvoice(event.data);
      break;
    case 'document.uploaded':
      await handleDocument(event.data);
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }
}

Step 3: Handle Shipment Milestones

async function handleMilestone(data: {
  shipment_id: string;
  milestone: string;
  occurred_at: string;
  location?: { name: string; country: string };
}) {
  console.log(`Milestone: ${data.milestone} for ${data.shipment_id}`);
  console.log(`  At: ${data.occurred_at} | Location: ${data.location?.name}`);

  // Update your database
  await db.shipments.update({
    where: { flexportId: data.shipment_id },
    data: {
      status: data.milestone,
      lastMilestoneAt: new Date(data.occurred_at),
      currentLocation: data.location?.name,
    },
  });

  // Notify stakeholders for key milestones
  if (['departed', 'arrived', 'delivered'].includes(data.milestone)) {
    await notifyStakeholders(data.shipment_id, data.milestone);
  }
}

Step 4: Idempotent Processing

// Flexport may retry webhooks — ensure idempotent handling
async function processWebhookIdempotently(event: any) {
  const eventId = event.id || crypto.createHash('sha256')
    .update(JSON.stringify(event)).digest('hex');

  // Check if already processed
  const exists = await db.webhookLog.findUnique({ where: { eventId } });
  if (exists) {
    console.log(`Duplicate webhook ${eventId}, skipping`);
    return;
  }

  await db.$transaction([
    db.webhookLog.create({ data: { eventId, type: event.type, processedAt: new Date() } }),
    routeEvent(event),
  ]);
}

Error Handling

| Issue | Cause | Solution | |-------|-------|----------| | 401 signature mismatch | Wrong secret or body parsing | Use express.raw(), verify secret matches Portal | | Duplicate events | Flexport retries on timeout | Implement idempotency with event ID dedup | | Missing events | Endpoint unreachable | Monitor uptime, use dead letter queue | | Slow processing | Complex handler logic | Acknowledge fast (200), process async |

Resources

Next Steps

For performance optimization, see flexport-performance-tuning.