Skip to content

Webhooks

Webhooks

Subscribe to webhooks to receive notifications when tax rates or thresholds change.

Create Webhook

POST /v1/webhooks
Terminal window
curl -X POST https://api.shipvat.com/v1/webhooks \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/shipvat",
"events": ["rate.updated", "threshold.updated"],
"description": "Production webhook"
}'

Response

{
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/shipvat",
"events": ["rate.updated", "threshold.updated"],
"status": "active",
"description": "Production webhook",
"secret": "whsec_abc123xyz...",
"consecutive_failures": 0,
"last_failed_at": null,
"last_succeeded_at": null,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}

List Webhooks

GET /v1/webhooks
Terminal window
curl https://api.shipvat.com/v1/webhooks \
-H "Authorization: Bearer sk_live_..."

Response

{
"webhooks": [
{
"id": "wh_abc123",
"url": "https://your-app.com/webhooks/shipvat",
"events": ["rate.updated", "threshold.updated"],
"status": "active",
"description": "Production webhook",
"consecutive_failures": 0,
"last_succeeded_at": "2024-01-15T12:00:00Z",
"created_at": "2024-01-15T10:30:00Z"
}
],
"total": 1
}

Get Webhook

GET /v1/webhooks/:id

Update Webhook

PUT /v1/webhooks/:id
Terminal window
curl -X PUT https://api.shipvat.com/v1/webhooks/wh_abc123 \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"events": ["rate.updated", "threshold.updated", "sync.completed"],
"status": "active"
}'

Delete Webhook

DELETE /v1/webhooks/:id

Rotate Secret

Generate a new webhook secret. The old secret becomes invalid immediately.

POST /v1/webhooks/:id/rotate-secret
Terminal window
curl -X POST https://api.shipvat.com/v1/webhooks/wh_abc123/rotate-secret \
-H "Authorization: Bearer sk_live_..."

Response

{
"id": "wh_abc123",
"secret": "whsec_new_secret_xyz..."
}

List Event Types

GET /v1/webhooks/events/list
Terminal window
curl https://api.shipvat.com/v1/webhooks/events/list \
-H "Authorization: Bearer sk_live_..."

Response

{
"events": [
{
"type": "rate.updated",
"description": "A tax rate has been updated"
},
{
"type": "threshold.updated",
"description": "A nexus threshold has been updated"
},
{
"type": "jurisdiction.added",
"description": "A new jurisdiction has been added"
},
{
"type": "sync.completed",
"description": "Data synchronization completed"
}
],
"total": 4
}

Webhook Payloads

rate.updated

{
"event": "rate.updated",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"jurisdiction_id": "DE",
"jurisdiction_name": "Germany",
"tax_type": "VAT",
"old_rate": 0.19,
"new_rate": 0.20,
"effective_from": "2024-07-01"
}
}

threshold.updated

{
"event": "threshold.updated",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"jurisdiction_id": "US-CA",
"jurisdiction_name": "California",
"threshold_type": "REMOTE_SELLER_NEXUS",
"old_amount": 500000,
"new_amount": 750000,
"currency": "USD",
"effective_from": "2024-07-01"
}
}

sync.completed

{
"event": "sync.completed",
"timestamp": "2024-01-15T02:00:00Z",
"data": {
"region": "EU",
"records_updated": 27,
"duration_ms": 1234
}
}

Verifying Webhooks

All webhook payloads are signed using HMAC-SHA256. Verify the signature to ensure the request is from ShipVAT.

Headers

HeaderDescription
X-ShipVAT-SignatureHMAC-SHA256 signature
X-ShipVAT-TimestampUnix timestamp

Verification (Node.js)

import crypto from 'crypto';
function verifyWebhook(payload, signature, timestamp, secret) {
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
app.post('/webhooks/shipvat', (req, res) => {
const signature = req.headers['x-shipvat-signature'];
const timestamp = req.headers['x-shipvat-timestamp'];
if (!verifyWebhook(req.rawBody, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
console.log(`Received ${event.event}:`, event.data);
res.status(200).send('OK');
});

Retry Policy

  • Failed webhooks are retried up to 5 times
  • Retry delays: 1min, 5min, 30min, 2hr, 24hr
  • After 5 consecutive failures, the webhook is disabled
  • Re-enable by updating status to active