Webhooks Guide

Vectis sends real-time notifications to your application when events occur. Configure webhook endpoints to receive order updates, inventory changes, and fulfillment events.

Setting Up Webhooks

Create webhooks through the API for now (the dashboard form doesn’t send the required signing secret). Manage, test and view deliveries in Settings → Webhooks.

Your signing secret is the secret you send when you create the webhook (at least 16 characters). Vectis doesn’t display it later, so store it when you create the webhook.

Via API

curl -X POST https://acme.vectisoms.app/api/webhooks \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.yoursite.com/webhooks/vectis",
    "events": ["OrderCreated", "OrderShipped"],
    "secret": "whsec_your_secret_here",
    "isActive": true
  }'

Event Types

Use * to receive every event.

Order Events

EventDescription
OrderCreatedNew order received
OrderConfirmedOrder confirmed for fulfillment
OrderCancelledOrder was cancelled
OrderShippedAll packages shipped
OrderDeliveredOrder delivered

Package Events

EventDescription
PackageCreatedPackage created for order
PackageShippedPackage shipped with tracking
PackageDeliveredPackage delivered

Inventory Events

EventDescription
InventoryAdjustedStock level changed

Webhook Payload

Every webhook body has event, timestamp, and data. data varies by event and may include fields not shown here.

{
  "event": "OrderShipped",
  "timestamp": "2026-03-15T10:30:00Z",
  "data": {
    "orderId": "550e8400-e29b-41d4-a716-446655440000",
    "trackingNumber": "1Z999AA10123456784",
    "carrier": "UPS",
    "shippedAt": "2026-03-15T10:29:58Z",
    "eventId": "..."
  }
}

Verifying Signatures

All outbound webhooks are signed with HMAC-SHA256. Always verify signatures before processing webhook data.

Headers

Each webhook request includes:

  • X-Vectis-Signature: t=<timestamp>,v1=<signature> (Unix seconds and hex HMAC-SHA256)
  • X-Vectis-Event: the event name
  • X-Vectis-Delivery-Id: unique per delivery

Verification Process

  1. Take the timestamp t from X-Vectis-Signature and concatenate: {t}.{raw_request_body}
  2. Compute HMAC-SHA256 using your webhook secret
  3. Compare with the v1 value from X-Vectis-Signature (timing-safe)
  4. Reject if timestamp is older than 5 minutes

Node.js Example

const crypto = require('crypto');

function verifyWebhook(payload, header, secret) {
  // Parse t (timestamp) and v1 (signature) from X-Vectis-Signature
  const { t, v1 } = Object.fromEntries(header.split(',').map(p => p.split('=')));

  // Check timestamp freshness (5 minute window)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(t)) > 300) {
    return false;
  }

  // Compute expected signature
  const signedPayload = `${t}.${payload}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  // Timing-safe comparison (lengths must match first, or timingSafeEqual throws)
  const received = Buffer.from(v1 || '');
  const computed = Buffer.from(expected);
  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}

// Express: keep the raw body for this route
app.post('/webhooks/vectis', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.headers['x-vectis-signature'] || '';
  const payload = req.body.toString('utf8'); // raw body, not parsed JSON

  if (!verifyWebhook(payload, header, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(payload);
  // Process event...

  res.status(200).json({ received: true });
});

Python Example

import hmac
import hashlib
import time

def verify_webhook(payload: bytes, header: str, secret: str) -> bool:
    # Parse t (timestamp) and v1 (signature) from X-Vectis-Signature
    parts = dict(p.split("=", 1) for p in header.split(","))
    timestamp, signature = parts["t"], parts["v1"]

    # Check timestamp freshness
    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        return False

    # Compute expected signature
    signed_payload = f"{timestamp}.{payload.decode()}"
    expected = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    # Timing-safe comparison
    return hmac.compare_digest(signature, expected)

Retry Behavior

Vectis makes up to three delivery attempts; after the third failure the delivery is marked failed and is not retried automatically. Your webhook stays active, and you can retry a failed delivery yourself (see Managing Webhooks).

A delivery is considered failed if:

  • Your endpoint returns a non-2xx status code
  • The request times out (30 seconds)
  • Connection cannot be established

Best Practices

  1. Respond quickly — Return a 200 response immediately, then process asynchronously
  2. Handle duplicates: use data.eventId (or the X-Vectis-Delivery-Id header) to deduplicate.
  3. Verify signatures — Never process unverified webhooks
  4. Use HTTPS — Webhook endpoints must use HTTPS
  5. Log everything — Store raw payloads for debugging

Testing Webhooks

Use the Test button in your dashboard to send a webhook.test event. Only webhooks subscribed to all events (*) receive it. You can also use tools like webhook.site during development.

Managing Webhooks

List Webhooks

curl https://acme.vectisoms.app/api/webhooks \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Delete Webhook

curl -X DELETE https://acme.vectisoms.app/api/webhooks/{id} \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Delivery History and Retries

Open a webhook’s deliveries in Settings → Webhooks to see each delivery’s status, latest response code and response time, and number of attempts, and retry a failed delivery. Through the API: GET /api/webhooks/{id}/deliveries and POST /api/webhooks/{id}/deliveries/{deliveryId}/retry.