# Webhooks Juniper sends real-time notifications to your server when order status changes. Configure your webhook URL in [Partner Settings](/apis/#tag/Partner-Settings) to receive these updates automatically. ## Configuration Set your webhook endpoint using the Partner Settings API: ```bash curl -X PATCH https://api.fulfillment.sandbox.juniperhealth.com/v1/partner-settings \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "webhookUrl": "https://your-server.com/webhooks/juniper" }' ``` ## Webhook Payload When an order status changes, Juniper sends an HTTP POST request to your webhook URL with the following payload structure: ```json { "order": { "orderId": "2vSGym0bH8qVEwCIGlyFoRgJq1A", "status": "SHIPPED", "fulfillmentInfo": { "carrier": "UPS", "trackingNumber": "1Z9999999999999999", "trackingStatus": "InTransit", "trackingUrl": "https://www.ups.com/track?tracknum=1Z9999999999999999", "checkpoints": [ { "checkpointTime": "2023-10-02T08:00:00Z", "location": "Los Angeles, CA, USA", "message": "Shipment picked up", "tag": "InTransit" }, { "checkpointTime": "2023-10-03T14:00:00Z", "location": "Chicago, IL, USA", "message": "Package arrived at facility", "tag": "InTransit" } ] }, "createdAt": "2023-10-01T12:00:00Z", "updatedAt": "2023-10-05T14:30:00Z" } } ``` ### Payload Fields | Field | Type | Description | | --- | --- | --- | | `order.orderId` | string | Unique order identifier | | `order.status` | string | Current order status (see [Status Events](#status-events)) | | `order.fulfillmentInfo` | object | Shipping details (present when shipped) | | `order.fulfillmentInfo.carrier` | string | Shipping carrier name (e.g., "UPS", "USPS", "FedEx") | | `order.fulfillmentInfo.trackingNumber` | string | Carrier tracking number | | `order.fulfillmentInfo.trackingStatus` | string | Current tracking status | | `order.fulfillmentInfo.trackingUrl` | string | URL to track the shipment | | `order.fulfillmentInfo.checkpoints` | array | Tracking checkpoint history | | `order.createdAt` | string | Order creation timestamp (ISO 8601) | | `order.updatedAt` | string | Order last update timestamp (ISO 8601) | ### Checkpoint Fields | Field | Type | Description | | --- | --- | --- | | `checkpointTime` | string | Timestamp of the checkpoint event (ISO 8601) | | `location` | string | Location of the shipment at this checkpoint | | `message` | string | Status message from the carrier | | `tag` | string | Status tag (e.g., "InTransit", "OutForDelivery", "Delivered") | ## Status Events Webhooks are triggered for the following order status changes: | Status | Description | | --- | --- | | `NEW` | Order received and validated | | `PROCESSING` | Order is being prepared | | `DISPENSING` | Order is being dispensed/filled | | `FILLED` | Order has been filled | | `FULFILLED` | Order fulfillment complete | | `AWAITING_SHIPMENT` | Order is packaged and ready to ship | | `SHIPPED` | Order has shipped (includes `fulfillmentInfo` with tracking) | | `CANCELLED` | Order was cancelled | ## Handling Webhooks ### Best Practices 1. **Return 2xx quickly** - Respond with a 2xx status code within 30 seconds to acknowledge receipt 2. **Process asynchronously** - Queue webhook payloads for background processing 3. **Handle idempotently** - The same event may be sent multiple times; use `orderId` and `updatedAt` to deduplicate 4. **Verify the source** - Validate requests originate from Juniper's IP ranges ### Example Handler (Node.js) ```javascript app.post('/webhooks/juniper', async (req, res) => { // Acknowledge receipt immediately res.status(200).send('OK'); // Process asynchronously const { order } = req.body; switch (order.status) { case 'SHIPPED': await notifyCustomer(order.orderId, order.fulfillmentInfo); break; case 'CANCELLED': await handleCancellation(order.orderId); break; // Handle other statuses... } }); ``` ### Example Handler (Python) ```python @app.route('/webhooks/juniper', methods=['POST']) def handle_webhook(): payload = request.get_json() order = payload.get('order', {}) # Queue for async processing queue.enqueue(process_order_update, order) return 'OK', 200 def process_order_update(order): if order['status'] == 'SHIPPED': notify_customer(order['orderId'], order.get('fulfillmentInfo')) elif order['status'] == 'CANCELLED': handle_cancellation(order['orderId']) ``` ## Retry Policy If your endpoint doesn't respond with a 2xx status code, Juniper will retry the webhook: - **Retry attempts**: 3 - **Retry intervals**: 1 minute, 5 minutes, 30 minutes - **Timeout**: 30 seconds per attempt After all retries are exhausted, the webhook is marked as failed. You can retrieve missed updates by polling the [Get Order](/apis/#operation/getOrder) endpoint.