Setup & Configuration
Learn how to create webhook endpoints, verify signatures, handle retries, and implement best practices for receiving real-time notifications from ShipOS.
Creating a Webhook Endpoint
1. Navigate to Webhook Settings
In the ShipOS Webhooks dashboard (opens the Cybership dashboard in a new tab):
- Go to Settings → Webhooks (opens the Cybership dashboard in a new tab)
- Click Create Webhook
2. Configure Your Webhook
Provide the following information:
- URL: Your HTTPS endpoint that will receive webhook events
- Description: A meaningful description for this webhook
- Contact Email: Email for notifications about webhook issues
- Events: Select which events to subscribe to:
- Use
*to receive all events - Use
orders/*to receive all order events - Use specific events like
orders/createdfor granular control
- Use
3. Save Your Webhook Secret
After creating a webhook, you'll receive a webhook secret. This is shown only once, so save it securely. You'll use this secret to verify that webhooks are genuinely from ShipOS.
Always check the specific event documentation to understand the exact payload format for each event type.
Example of webhook payload:
{
"id": "ord_1a2b3c4d5e6f7g8h9i0j",
"order_number": "#1001",
"fulfillment_status": "UNFULFILLED",
// ... rest of order data
}Verifying Webhook Signatures
Cybership signs all webhook payloads using HMAC-SHA256. Always verify signatures to ensure webhooks are from Cybership and haven't been tampered with.
Headers Included
Each webhook request includes these headers:
x-cybr-signature: The HMAC signaturex-cybr-event-id: Unique identifier for this eventx-cybr-event-type: The event type (e.g.,orders/created)x-cybr-timestamp: Unix timestamp when the webhook was sent
Signature Verification Example
Cybership uses the same signature scheme as Stripe and other modern webhook providers. The signature is generated using HMAC-SHA256 with the format: t={timestamp},v1={hash}.
import crypto from 'crypto';
function verifyWebhookSignature(payload, headers, secret) {
const signatureHeader = headers['x-cybr-signature'];
if (!signatureHeader) {
throw new Error('Missing signature header');
}
// Parse signature header (format: "t=1234567890,v1=hash1")
const signatureParts = {};
signatureHeader.split(',').forEach(part => {
const [key, value] = part.split('=');
if (key === 't') {
signatureParts.timestamp = value;
} else if (key === 'v1') {
if (!signatureParts.hashes) {
signatureParts.hashes = [];
}
signatureParts.hashes.push(value);
}
});
const timestamp = signatureParts.timestamp;
const receivedHashes = signatureParts.hashes;
if (!timestamp || !receivedHashes || receivedHashes.length === 0) {
throw new Error('Invalid signature format');
}
// Check timestamp to prevent replay attacks (5 minute tolerance)
const currentTime = Math.floor(Date.now() / 1000);
const timeDifference = currentTime - parseInt(timestamp);
if (Math.abs(timeDifference) > 300) {
throw new Error('Webhook timestamp too old');
}
// Construct signed payload and compute hash
const signedPayload = `${timestamp},${payload}`;
const hmac = crypto.createHmac('sha256', secret);
hmac.update(signedPayload);
const computedHash = hmac.digest('hex');
// Compare hashes using timing-safe comparison
let signatureValid = false;
for (const receivedHash of receivedHashes) {
if (crypto.timingSafeEqual(
Buffer.from(computedHash),
Buffer.from(receivedHash)
)) {
signatureValid = true;
break;
}
}
if (!signatureValid) {
throw new Error('Invalid webhook signature');
}
return true;
}
// Express.js example
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body.toString();
try {
verifyWebhookSignature(payload, req.headers, process.env.WEBHOOK_SECRET);
const event = JSON.parse(payload);
// Process the event
switch (event.type) {
case 'orders/created':
handleOrderCreated(event.data);
break;
// ... handle other events
}
// Always respond quickly with 2xx status
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(400).send('Bad Request');
}
});Webhook Delivery & Retries
Delivery Requirements
Your endpoint must:
- Accept HTTPS POST requests
- Return a 2xx status code within 30 seconds
- Handle duplicate events idempotently (use the event ID)
Retry Schedule
If your endpoint fails to respond with a 2xx status code, we will retry with exponential backoff:
- 1st retry: 5 seconds after initial attempt
- 2nd retry: 25 seconds after 1st retry
- 3rd retry: 125 seconds after 2nd retry
- 4th retry: 625 seconds after 3rd retry
After 4 failed attempts, no further retries will be made.
Automatic Disabling
Webhooks are automatically disabled after 10 consecutive failures. When this happens:
- The webhook stops receiving events
- A notification email is sent to the contact email
- You must manually re-enable the webhook in the dashboard
You'll also receive a warning email after 5 consecutive failures.
Testing Your Webhook
Using the Test Feature
- Go to your webhook in the Webhooks dashboard (opens the Cybership dashboard in a new tab)
- Click Test Webhook
- Select an event type and customize the payload
- Click Send Test Event
Local Development with ngrok
For local development, use ngrok to expose your local server:
# Start your local server
npm run dev
# In another terminal, expose it with ngrok
ngrok http 3000
# Use the HTTPS URL from ngrok as your webhook endpointMonitoring Webhook Deliveries
Viewing Delivery Logs
- Go to Settings → Webhooks (opens the Cybership dashboard in a new tab)
- Click on your webhook endpoint
- View the delivery history including:
- Event type and payload
- Response status code
- Response time
- Any error messages
Delivery States
- Success: Your endpoint returned a 2xx status code
- Failed: Your endpoint returned a non-2xx status code or timed out
- Pending: The webhook is queued for delivery or retry
Best Practices
1. Respond Quickly
Process webhooks asynchronously:
app.post('/webhooks', async (req, res) => {
// Verify signature
// ...
// Queue for processing instead of processing inline
await jobQueue.add('process-webhook', {
event: req.body
});
// Respond immediately
res.status(200).send('OK');
});Rate Limits
Webhook deliveries are subject to rate limits to ensure system stability:
- Per endpoint: 10 requests/second, 100 requests/minute, 1000 requests/hour
- Per team: 50 requests/second, 500 requests/minute, 5000 requests/hour
If you're approaching rate limits, consider:
- Subscribing to fewer events
- Batching operations in your handler
- Using multiple endpoints for different event types
Security Considerations
- Always verify signatures - Never process webhooks without verification
- Use HTTPS only - Webhooks are only sent to HTTPS endpoints
- Validate event data - Don't trust webhook data blindly
- Implement timeouts - Reject webhooks with old timestamps
- Store secrets securely - Use environment variables or secret management services
- Implement access controls - Restrict who can modify webhook endpoints