Webhooks
Subscribe to events and receive real-time notifications when things happen in your account.
Event Types
| Event | Description |
|---|---|
order.created | A new order was created |
order.updated | An order was updated (status, address, etc.) |
shipment.created | A shipping label was purchased |
shipment.tracking_updated | Tracking status changed for a shipment |
shipment.voided | A shipping label was voided/refunded |
Payload Format
All webhook deliveries use the same envelope:
{
"event": "shipment.created",
"timestamp": "1708300800",
"data": {
"id": "clx2ship1",
"trackingCode": "9400111899223100012345",
"carrier": "USPS",
"service": "Priority Mail",
"rate": 12.50
}
}
Delivery Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-ShipWave-Event | Event type (e.g. shipment.created) |
X-ShipWave-Signature | HMAC-SHA256 signature for verification |
X-ShipWave-Timestamp | Unix timestamp (seconds) when the event was sent |
Signature Verification
Every delivery is signed so you can verify it came from ShipWave.
Algorithm
- Concatenate
{timestamp}.{raw_body}(theX-ShipWave-Timestampheader, a dot, and the raw request body) - Compute HMAC-SHA256 using your webhook secret as the key
- Compare with the
X-ShipWave-Signatureheader (hex-encoded)
Verification Example (Node.js)
import crypto from "crypto";
function verifyWebhookSignature(
rawBody: string,
signature: string,
timestamp: string,
secret: string
): boolean {
const signedPayload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
}
Express Middleware Example
import express from "express";
import crypto from "crypto";
const WEBHOOK_SECRET = process.env.SHIPWAVE_WEBHOOK_SECRET!;
const TIMESTAMP_TOLERANCE_SEC = 300; // 5 minutes
app.post(
"/webhooks/shipwave",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-shipwave-signature"] as string;
const timestamp = req.headers["x-shipwave-timestamp"] as string;
const rawBody = req.body.toString();
// Replay protection: reject events older than 5 minutes
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (age > TIMESTAMP_TOLERANCE_SEC) {
return res.status(400).send("Timestamp too old");
}
// Verify signature
const signedPayload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(signedPayload)
.digest("hex");
const valid = crypto.timingSafeEqual(
Buffer.from(signature, "hex"),
Buffer.from(expected, "hex")
);
if (!valid) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(rawBody);
console.log(`Received ${event.event}`, event.data);
// Respond quickly — process asynchronously
res.sendStatus(200);
}
);
Replay Protection
Check the X-ShipWave-Timestamp header to prevent replay attacks. Reject events where the timestamp is more than 5 minutes from the current time.
Failure Handling
ShipWave retries failed deliveries with exponential backoff (up to 25 attempts). If a subscription accumulates 10 consecutive failures, it is automatically disabled.
To re-enable a disabled subscription, update it with isActive: true — this also resets the failure counter.
List Webhook Subscriptions
GET /api/v1/webhooks
Returns all webhook subscriptions for your account. The secret is not included in list responses.
Example
curl https://shipwave.app/api/v1/webhooks \
-H "Authorization: Bearer sw_live_abc123..."
Response
{
"data": [
{
"id": "clx4wh1",
"url": "https://myapp.com/webhooks/shipwave",
"events": ["order.created", "shipment.created"],
"isActive": true,
"failureCount": 0,
"lastSentAt": "2026-02-18T20:00:00.000Z",
"lastErrorAt": null,
"lastError": null,
"createdAt": "2026-02-01T12:00:00.000Z",
"updatedAt": "2026-02-18T20:00:00.000Z"
}
]
}
Create Webhook Subscription
POST /api/v1/webhooks
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | yes | HTTPS endpoint URL |
events | string[] | yes | Event types to subscribe to |
The URL must use HTTPS. The events array must contain at least one valid event type.
Example
curl -X POST https://shipwave.app/api/v1/webhooks \
-H "Authorization: Bearer sw_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://myapp.com/webhooks/shipwave",
"events": ["order.created", "shipment.created", "shipment.tracking_updated"]
}'
const res = await fetch("https://shipwave.app/api/v1/webhooks", {
method: "POST",
headers: {
Authorization: "Bearer sw_live_abc123...",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://myapp.com/webhooks/shipwave",
events: ["order.created", "shipment.created", "shipment.tracking_updated"],
}),
});
const { data } = await res.json(); // 201 Created
Response (201 Created)
The secret is included only in the creation response. Store it securely — it cannot be retrieved later.
{
"data": {
"id": "clx4wh1",
"url": "https://myapp.com/webhooks/shipwave",
"events": ["order.created", "shipment.created", "shipment.tracking_updated"],
"secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
"isActive": true,
"createdAt": "2026-02-19T10:00:00.000Z"
}
}
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid URL (not HTTPS), empty events array, or invalid event type |
Update Webhook Subscription
PUT /api/v1/webhooks/:id
Updatable Fields
| Field | Type | Description |
|---|---|---|
url | string | New HTTPS endpoint URL |
events | string[] | Updated event types (non-empty) |
isActive | boolean | Enable or disable the subscription |
Setting isActive: true resets the failure counter.
Example
curl -X PUT https://shipwave.app/api/v1/webhooks/clx4wh1 \
-H "Authorization: Bearer sw_live_abc123..." \
-H "Content-Type: application/json" \
-d '{
"events": ["order.created", "order.updated", "shipment.created", "shipment.voided"],
"isActive": true
}'
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid URL or events |
| 403 | FORBIDDEN | Subscription exists but belongs to another user |
| 404 | NOT_FOUND | Subscription not found |
Delete Webhook Subscription
DELETE /api/v1/webhooks/:id
Permanently deletes a webhook subscription. Pending deliveries for this subscription will not be retried.
Example
curl -X DELETE https://shipwave.app/api/v1/webhooks/clx4wh1 \
-H "Authorization: Bearer sw_live_abc123..."
Response
{
"data": {
"id": "clx4wh1",
"deleted": true
}
}
Errors
| Status | Code | Cause |
|---|---|---|
| 403 | FORBIDDEN | Subscription exists but belongs to another user |
| 404 | NOT_FOUND | Subscription not found |