Webhooks
Webhooks
Subscribe to webhooks to receive notifications when tax rates or thresholds change.
Create Webhook
POST /v1/webhookscurl -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/webhookscurl 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/:idUpdate Webhook
PUT /v1/webhooks/:idcurl -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/:idRotate Secret
Generate a new webhook secret. The old secret becomes invalid immediately.
POST /v1/webhooks/:id/rotate-secretcurl -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/listcurl 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
| Header | Description |
|---|---|
X-ShipVAT-Signature | HMAC-SHA256 signature |
X-ShipVAT-Timestamp | Unix 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 handlerapp.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
statustoactive