# Webhooks Overview Webhooks enable you to receive near real-time notifications when events occur in your MVMNT account. Instead of polling the API for changes, MVMNT sends HTTP POST requests to your server when important events happen. ## What Are Webhooks? A webhook is an HTTP callback - an HTTP POST request sent to a URL you configure. When an event occurs in MVMNT (like an order status change), we send a JSON payload to your webhook endpoint. ``` ┌──────────────┐ Event Occurs ┌──────────────┐ │ MVMNT │ (Order Booked) │ Your MVMNT │ │ System │────────────────────────────▶│ Account │ └──────────────┘ └──────────────┘ │ │ ┌──────────────▼──────────────┐ │ Event Processing Pipeline │ │ (EventBridge + Lambda) │ └──────────────┬──────────────┘ │ │ HTTP POST │ ┌──────────────▼──────────────┐ │ Your Webhook Endpoint │ │ https://your-app.com/hooks │ └──────────────────────────────┘ ``` ## Why Use Webhooks? ### ✅ Benefits - **Real-Time Updates**: Get notified within seconds of events occurring - **Reduced API Calls**: No need to poll for changes (saves on rate limits) - **Better Performance**: Lower latency than polling - **Event-Driven Architecture**: Build reactive, event-driven integrations - **Bandwidth Efficient**: Only receive data when something changes ### ❌ Polling Alternative (Not Recommended) Without webhooks, you'd need to poll: ```javascript // ❌ Don't do this - wastes API calls and has high latency setInterval(async () => { const shipments = await api.get('/v1/shipments?status=in_transit'); shipments.forEach(checkForUpdates); }, 30000); // Poll every 30 seconds ``` ### ✅ Webhook Approach (Recommended) ```javascript // ✅ Do this - real-time, efficient app.post('/webhooks/mvmnt', (req, res) => { const { events } = req.body; events.forEach((event) => { if (event.event === 'SHIPMENT_DELIVERED') { handleShipmentUpdate(event.data); } }); res.status(200).send('OK'); }); ``` ## How Webhooks Work ### 1. Configure Webhook in MVMNT UI Set up your webhook in the MVMNT application: - **URL**: Your endpoint (must be HTTPS) - **Token**: Secret token that MVMNT will send in the `x-api-key` header - **Events**: Select which event types to receive (e.g., `SHIPMENT_DELIVERED`, `SHIPMENT_BOOKED`) The webhook configuration is managed through the MVMNT UI settings page. ### 2. Receive Webhook Requests When an event occurs, MVMNT sends an HTTP POST request to your URL: ```http POST /webhooks/mvmnt HTTP/1.1 Host: your-app.com Content-Type: application/json x-api-key: your-webhook-token-from-ui { "sentAt": "2025-01-15T14:30:00Z", "events": [ { "event": "SHIPMENT_DELIVERED", "timestamp": "2025-01-15T14:30:00Z", "data": { "id": "660e8400-e29b-41d4-a716-446655440000", "friendlyId": "SHP-12345", "key": "ERP-SHIP-789" } } ] } ``` ### 3. Verify Token Always verify the `x-api-key` header matches your configured webhook token: ```javascript app.post('/webhooks/mvmnt', (req, res) => { const receivedToken = req.headers['x-api-key']; const expectedToken = process.env.WEBHOOK_TOKEN; // Token you configured in MVMNT UI if (receivedToken !== expectedToken) { return res.status(401).send('Invalid token'); } // Token is valid - process webhook console.log('Valid webhook:', req.body); res.status(200).send('OK'); }); ``` ### 4. Respond Quickly Respond with `200 OK` within 5 seconds: ```javascript app.post('/webhooks/mvmnt', async (req, res) => { // Verify token if (req.headers['x-api-key'] !== process.env.WEBHOOK_TOKEN) { return res.status(401).send('Invalid token'); } // Respond quickly res.status(200).send('OK'); // Process asynchronously processWebhookAsync(req.body); }); ``` ### 5. Handle Retries If your endpoint fails or doesn't respond, MVMNT automatically retries with exponential backoff: - **1st retry**: 1 minute - **2nd retry**: 5 minutes - **3rd retry**: 15 minutes - **4th retry**: 1 hour - **5th retry**: 4 hours After 5 failed attempts, the webhook delivery is abandoned. ## Webhook Payload Structure All webhook deliveries follow this structure: ```json { "sentAt": "2025-01-15T14:30:00Z", "events": [ { "event": "SHIPMENT_DELIVERED", "timestamp": "2025-01-15T14:30:00Z", "data": { // Event-specific data // Always includes: id, friendlyId, key (if set) }, "diff": null } ] } ``` ### Top-Level Fields Every webhook delivery includes: | Field | Type | Description | | --- | --- | --- | | `sentAt` | string (ISO 8601) | When the webhook was sent from MVMNT | | `events` | array | Array of events (typically one event) | ### Event Fields Each event in the `events` array includes: | Field | Type | Description | | --- | --- | --- | | `event` | string | Event type (e.g., `SHIPMENT_DELIVERED`) | | `timestamp` | string (ISO 8601) | When the event occurred | | `data` | object | Event-specific data | | `data.id` | string (UUID) | Resource ID | | `data.friendlyId` | string | Human-readable ID (e.g., `SHP-12345`) | | `data.key` | string | null | Your system's reference ID (if set) | | `diff` | array | null | Changes made (only for UPDATE events, else null) | ### Event-Specific Fields Each event type includes additional relevant fields in the `data` object. The exact structure varies by event type. **Example: SHIPMENT_DELIVERED** ```json { "sentAt": "2025-01-15T14:30:00Z", "events": [ { "event": "SHIPMENT_DELIVERED", "timestamp": "2025-01-15T14:30:00Z", "data": { "id": "660e8400-e29b-41d4-a716-446655440000", "friendlyId": "SHP-12345", "key": "ERP-SHIP-789" }, "diff": null } ] } ``` **Example: CARRIER_UPDATED (with diff)** ```json { "sentAt": "2025-01-15T14:30:00Z", "events": [ { "event": "CARRIER_UPDATED", "timestamp": "2025-01-15T14:30:00Z", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "friendlyId": "CAR-12345", "key": "ERP-CARRIER-789", "status": "INACTIVE" }, "diff": [ { "type": "UPDATE", "key": "status", "value": "INACTIVE", "oldValue": "ACTIVE" } ] } ] } ``` **Note:** See the [API Reference](/apis/openapi) for the complete schema of each webhook payload type. ## Quick Start ### 1. Create a Test Endpoint ```javascript const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhooks/mvmnt', (req, res) => { const { sentAt, events } = req.body; console.log(`Received ${events.length} event(s) sent at ${sentAt}`); events.forEach((event) => { console.log('Event:', event.event, 'Data:', event.data); }); res.status(200).send('OK'); }); app.listen(3000, () => { console.log('Webhook server listening on port 3000'); }); ``` ### 2. Expose with ngrok (for testing) ```bash ngrok http 3000 ``` Copy the HTTPS URL (e.g., `https://abc123.ngrok.io`) ### 3. Configure in MVMNT UI 1. Go to MVMNT Settings → Webhooks 2. Add your ngrok URL: `https://abc123.ngrok.io/webhooks/mvmnt` 3. Generate and copy a webhook token 4. Select event types (e.g., `SHIPMENT_DELIVERED`, `SHIPMENT_BOOKED`) 5. Save configuration ### 4. Update Your Code ```javascript // Use the token from MVMNT UI const WEBHOOK_TOKEN = 'your-token-from-mvmnt-ui'; ``` ### 5. Trigger Test Event Create or update a shipment in the MVMNT UI, and watch your endpoint receive the webhook! ## Best Practices ### ✅ Do - **Use HTTPS** (required for production) - **Verify token** on all webhook requests using the `x-api-key` header - **Respond quickly** (< 5 seconds) with 200 OK - **Process asynchronously** - don't block the response - **Use `key`** for easy correlation with your system - **Handle duplicate events** (webhooks may be delivered more than once) - **Log webhook deliveries** for debugging - **Test thoroughly** before going to production ### ❌ Don't - **Don't use HTTP** (only HTTPS allowed in production) - **Don't perform long operations** before responding - **Don't return errors** for duplicate events (return 200) - **Don't rely on event ordering** (events may arrive out of order) - **Don't skip token verification** (security risk) ## Managing Webhooks All webhook configuration is managed through the MVMNT UI: 1. **Settings → Webhooks** - Configure URL, token, and event types 2. **Enable/Disable** - Toggle webhook delivery on/off 3. **Update Events** - Add or remove event types to receive 4. **Rotate Token** - Generate a new token for security 5. **View Logs** - Monitor webhook deliveries and failures ## Troubleshooting ### Common Issues 1. **Token verification fails**: - Ensure you're using the correct token from MVMNT UI - Check that `x-api-key` header is being read correctly - Verify no extra whitespace in token comparison 2. **Endpoint unreachable**: - Verify URL and firewall settings - Ensure HTTPS certificate is valid - Check that your server is publicly accessible 3. **Timeout errors**: - Endpoint must respond within 5 seconds - Move processing to background jobs - Return 200 OK immediately 4. **Duplicate webhooks**: - This is expected behavior due to retries - Implement idempotency using event IDs - Always return 200 even for duplicates ## Next Steps - [API Reference](/apis/openapi) - View webhook schemas and event types