Webhooks

Subscribe to events and receive real-time notifications when things happen in your account.

Event Types

EventDescription
order.createdA new order was created
order.updatedAn order was updated (status, address, etc.)
shipment.createdA shipping label was purchased
shipment.tracking_updatedTracking status changed for a shipment
shipment.voidedA 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

HeaderDescription
Content-Typeapplication/json
X-ShipWave-EventEvent type (e.g. shipment.created)
X-ShipWave-SignatureHMAC-SHA256 signature for verification
X-ShipWave-TimestampUnix timestamp (seconds) when the event was sent

Signature Verification

Every delivery is signed so you can verify it came from ShipWave.

Algorithm

  1. Concatenate {timestamp}.{raw_body} (the X-ShipWave-Timestamp header, a dot, and the raw request body)
  2. Compute HMAC-SHA256 using your webhook secret as the key
  3. Compare with the X-ShipWave-Signature header (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

FieldTypeRequiredDescription
urlstringyesHTTPS endpoint URL
eventsstring[]yesEvent 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

StatusCodeCause
400VALIDATION_ERRORInvalid URL (not HTTPS), empty events array, or invalid event type

Update Webhook Subscription

PUT /api/v1/webhooks/:id

Updatable Fields

FieldTypeDescription
urlstringNew HTTPS endpoint URL
eventsstring[]Updated event types (non-empty)
isActivebooleanEnable 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

StatusCodeCause
400VALIDATION_ERRORInvalid URL or events
403FORBIDDENSubscription exists but belongs to another user
404NOT_FOUNDSubscription 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

StatusCodeCause
403FORBIDDENSubscription exists but belongs to another user
404NOT_FOUNDSubscription not found