Webhook V2

   


   

Table of Contents Introduction How Webhooks Work Setting Up a Webhook Endpoint Webhook Notifications HTTP Headers Event Types Request Body Structure Webhook Verification Example in Node.js Example in Java Example in Go Example in PHP Shipment Status Codes Best Practices Troubleshooting Common Issues Webhook Retry Policy Testing Your Webhook Support Introduction ParcelPanel Webhooks allow your application to receive real-time notifications about shipment events. Instead of continuously polling our system for updates, webhooks push information to your server as events occur, allowing you to build integrations that react immediately to changes in shipment status. This document describes how to integrate with ParcelPanel's Webhook system to receive shipment tracking updates. How Webhooks Work You register a webhook URL in your ParcelPanel account ParcelPanel sends HTTP POST requests to your URL when shipment events occur Your server processes these notifications and takes appropriate actions Setting Up a Webhook Endpoint To receive webhooks from ParcelPanel, you need to: Create an HTTP endpoint on your server that can receive POST requests Register this endpoint URL in your ParcelPanel dashboard Implement verification logic to validate incoming webhooks Process the webhook data according to your business needs Webhook Notifications HTTP Headers Each webhook request includes the following HTTP headers: HeaderDescriptionContent-TypeAlways application/jsonX-ParcelPanel-HMAC-SHA256Base64-encoded HMAC-SHA256 signature for request verificationX-ParcelPanel-TopicType of event that triggered the webhook (e.g., shipment_status/delivered)X-ParcelPanel-Triggered-AtISO8601 timestamp when the webhook was triggeredX-ParcelPanel-Webhook-IdUnique identifier for the webhook requestX-ParcelPanel-Webhook-VersionVersion of the webhook (2.0) Event Types ParcelPanel generates webhook events for various shipment status changes: Event TypeDescriptionshipment_status/any_updateWhen any update to the shipment occursshipment_status/info_receivedWhen shipping information is received by carriershipment_status/in_transitWhen package is in transitshipment_status/out_for_deliveryWhen package is out for deliveryshipment_status/ready_for_pickupWhen package is ready for pickupshipment_status/deliveredWhen package is deliveredshipment_status/failed_attemptWhen delivery attempt failsshipment_status/exceptionWhen a delivery exception occurs Request Body Structure Webhook notifications contain detailed information about the shipment event in JSON format: { "order_id": 6140516335690, "order_number": "#1030", "order_tags": ["ParcelPanel", "Webhook2.0"], "store": { "name": "Example Store", "url": "https://example.myshopify.com" }, "customer": { "name": "Aaliyah Bins", "email": "[email protected]", "phone": "12345678901" }, "shipping_address": { "name": "Aaliyah Bins", "phone": "12345678901", "country": "United States", "country_code": "US", "province": "California", "province_code": "CA", "city": "Mountain View", "zip": "94043", "address1": "1600 Amphitheatre Parkway", "address2": null, "company": "Googleplex" }, "tracking_link": "https://example.myshopify.com/apps/parcelpanel?nums=YT2436021211003147", "status": "DELIVERED", "status_label": "Delivered", "substatus": "Delivered_001", "substatus_label": "Delivered", "tracking_number": "YT2436021211003147", "carrier": { "name": "YunExpress", "code": "yunexpress", "contact": "400-8575-500", "logo_url": "https://cdn.parcelpanel.com/assets/common/images/express/yunexpress.png", "url": "http://www.yuntrack.com/track/detail?id=YT2436021211003147" }, "transit_time": 11, "residence_time": 1, "estimated_delivery_date": { "source": "CUSTOM", "display_text": "Jan 12, 2025 - Jan 16, 2025" }, "order_date": "2024-12-23T01:02:40+00:00", "fulfillment_date": "2025-12-24T01:25:40+00:00", "pickup_date": "2025-01-03T01:28:40", "delivery_date": "2025-01-13T14:36:00", "last_mile_tracking_supported": true, "last_mile": { "carrier_name": "USPS", "carrier_code": "usps", "tracking_number": "9261290358097849005373", "carrier_contact": "1 800-275-8777", "carrier_logo_url": "https://cdn.parcelpanel.com/assets/common/images/express/usps.png", "carrier_url": "https://tools.usps.com/go/TrackConfirmAction?qtc_tLabels1=9261290358097849005373" }, "products": [ { "id": 6941337714762, "title": "Amazing Aluminum Bag Collection10", "variant_id": 39969756151882, "variant_title": "xs / red / water", "quantity": 3, "image_url": "https://cdn.shopify.com/s/files/1/0563/3435/2458/products/AAUvwnj0ICORVuxs41ODOvnhvedArLiSV20df7r8XBjEUQ_s900-c-k-c0x00ffffff-no-rj_147ad8db-b4f4-40a5-b751-c0a8428b8ae7-951612.jpg?v=1678447639", "url": "https://example.myshopify.com/apps/parcelpanel/bland?q=eyJ0IjoxNzQ1NTY1MzAzLCJwcm9kdWN0X2lkIjo2OTQxMzM3NzE0NzYyLCJoYW5kbGUiOiJhbWF6aW5nLWFsdW1pbnVtLWJhZy1jb2xsZWN0aW9uMTAiLCJmcm9tIjoicGFyY2VscGFuZWwtYWRtaW4ifQ" } ], "checkpoints": [ { "detail": "Delivered, In/At Mailbox, FAIRBANKS, AK 99701", "status": "DELIVERED", "status_label": "Delivered", "substatus": "Delivered_001", "substatus_label": "Delivered", "checkpoint_time": "2025-01-13T14:36:00" }, { "detail": "Out for Delivery, FAIRBANKS, AK 99701", "status": "OUT_FOR_DELIVERY", "status_label": "Out for delivery", "substatus": "OutForDelivery_001", "substatus_label": "Out for delivery", "checkpoint_time": "2025-01-13T06:45:00" } ]} Webhook Verification To ensure webhook requests are coming from ParcelPanel, you should verify the signature included in each request: Retrieve the signature from the X-ParcelPanel-HMAC-SHA256 header Calculate an HMAC-SHA256 hash of the raw request body using your API key as the key Compare the calculated signature with the received signature If they match, process the webhook; otherwise, reject it Example in Node.js // Optimized Node.js example for webhook signature verificationconst crypto = require('crypto');function verifyWebhookSignature(requestBody, signature, apiKey) { const calculatedSignature = crypto .createHmac('sha256', apiKey) .update(requestBody) .digest('base64'); try { return crypto.timingSafeEqual( Buffer.from(calculatedSignature), Buffer.from(signature) ); } catch (e) { return false; }} Example in Java import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import java.nio.charset.StandardCharsets;import java.util.Base64;public class WebhookSignatureVerifier { public static boolean verifySignature(String requestBody, String signature, String apiKey) { try { Mac hmacSHA256 = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKey = new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); hmacSHA256.init(secretKey); byte[] calculatedSignatureBytes = hmacSHA256.doFinal(requestBody.getBytes(StandardCharsets.UTF_8)); String calculatedSignature = Base64.getEncoder().encodeToString(calculatedSignatureBytes); return signature.equals(calculatedSignature); } catch (Exception e) { return false; } }} Example in Go package mainimport ( "crypto/hmac" "crypto/sha256" "encoding/base64" "crypto/subtle")func VerifyWebhookSignature(requestBody []byte, signature string, apiKey string) bool { mac := hmac.New(sha256.New, []byte(apiKey)) mac.Write(requestBody) calculatedSignature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) return subtle.ConstantTimeCompare([]byte(calculatedSignature), []byte(signature)) == 1} Example in PHP function verifyWebhookSignature($requestBody, $signature, $apiKey) { $calculatedSignature = base64_encode(hash_hmac('sha256', $requestBody, $apiKey, true)); return hash_equals($calculatedSignature, $signature);} Note: For more information about HMAC-SHA256, please refer to the HMAC-SHA256 Wikipedia page. Shipment Status Codes ParcelPanel uses the following status codes to indicate the current state of a shipment: Status CodeDescriptionPENDINGOrder created but not yet shippedINFO_RECEIVEDShipping information received by carrierIN_TRANSITPackage is in transitOUT_FOR_DELIVERYPackage out for delivery todayREADY_FOR_PICKUPPackage is ready for pickupDELIVEREDPackage has been deliveredEXCEPTIONDelivery exception occurredFAILED_ATTEMPTDelivery attempt failedEXPIREDTracking information is no longer available Best Practices Respond quickly: Your endpoint should return a 2xx response as soon as possible Process asynchronously: Handle the webhook processing in a background job Handle duplicates: Implement idempotent processing to handle potential duplicate events Log events: Keep logs of received webhooks for debugging purposes Retry mechanism: Implement retry logic for any actions that might fail Troubleshooting Common Issues Not receiving webhooks Verify your endpoint is publicly accessible Check server logs for any errors Ensure your firewall allows incoming requests Signature verification failures Confirm you're using the correct API key Ensure you're calculating the signature using the raw request body Check for encoding issues Webhook processing errors Add robust error handling to your webhook processor Log all incoming webhook data for debugging Implement monitoring to detect processing failures Webhook Retry Policy If ParcelPanel cannot deliver a webhook (receives a non-2xx response code), it will retry with an exponential backoff algorithm for a total of 5 attempts: First retry: 10 seconds after failure Second retry: 30 seconds after failure Third retry: 60 seconds after failure Fourth retry: 120 seconds (2 minutes) after failure Fifth retry: 300 seconds (5 minutes) after failure After all 5 retry attempts are exhausted, the webhook will be dropped and will not be sent again. Testing Your Webhook You can test your webhook integration by: Setting up your endpoint to log all incoming requests Configuring the webhook URL in your ParcelPanel dashboard Triggering test events from the ParcelPanel webhook settings page Verifying your endpoint successfully processes the test webhooks Support If you're experiencing issues with webhooks or have questions about integration, please contact our support team at [email protected].