Webhooks
Real-time event notifications from CheckoutOS
Last updated: 2025-01-16
Webhooks allow you to receive real-time notifications when events occur in CheckoutOS. Instead of polling the API, webhooks push data to your server as events happen.
Available Events
Webhook Events
- upsell.accepted — Customer accepted an upsell offer
- upsell.declined — Customer declined an upsell offer
- bundle.purchased — Bundle was purchased
- experiment.concluded — A/B test reached significance
- order.edited — Customer edited their order
- order.cancelled — Customer cancelled their order
- subscription.created — Subscription upsell converted
Creating Webhooks
1
Navigate to Webhooks
Go to CheckoutOS → Settings → Webhooks.
2
Add Endpoint
Click "Add Webhook Endpoint".
3
Enter URL
Enter the HTTPS URL to receive events.
4
Select Events
Choose which events to subscribe to.
5
Save
Save and note the signing secret.
Create Webhook via API
curl -X POST "https://api.chargezen.com/v1/webhooks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourserver.com/webhooks/checkoutos",
"events": ["upsell.accepted", "bundle.purchased"],
"enabled": true
}'
# Response
{
"id": "wh_123",
"url": "https://yourserver.com/webhooks/checkoutos",
"events": ["upsell.accepted", "bundle.purchased"],
"signing_secret": "whsec_abc123...",
"enabled": true
}Webhook Payload
upsell.accepted
Upsell Accepted Event
{
"id": "evt_123456",
"type": "upsell.accepted",
"created_at": "2025-01-16T14:30:00Z",
"data": {
"upsell_id": "upsell_abc",
"upsell_name": "Premium Add-On",
"order_id": "order_789",
"shopify_order_id": "5678901234",
"customer_id": "cust_456",
"product": {
"id": "prod_xyz",
"title": "Premium Widget",
"variant_id": "var_123",
"price": 29.99
},
"discount": {
"type": "percentage",
"value": 15,
"amount": 4.50
},
"final_price": 25.49
}
}bundle.purchased
Bundle Purchased Event
{
"id": "evt_234567",
"type": "bundle.purchased",
"created_at": "2025-01-16T15:00:00Z",
"data": {
"bundle_id": "bundle_def",
"bundle_name": "Skincare Starter Kit",
"bundle_type": "fixed",
"order_id": "order_890",
"products": [
{ "id": "prod_a", "title": "Cleanser", "quantity": 1 },
{ "id": "prod_b", "title": "Toner", "quantity": 1 },
{ "id": "prod_c", "title": "Moisturizer", "quantity": 1 }
],
"original_price": 89.97,
"bundle_price": 71.98,
"savings": 17.99
}
}order.edited
Order Edited Event
{
"id": "evt_345678",
"type": "order.edited",
"created_at": "2025-01-16T15:30:00Z",
"data": {
"order_id": "order_901",
"shopify_order_id": "6789012345",
"edit_type": "add_items",
"changes": {
"items_added": [
{ "product_id": "prod_new", "quantity": 1, "price": 19.99 }
],
"items_removed": [],
"quantity_changes": []
},
"amount_changed": 19.99,
"new_total": 109.98
}
}Verifying Webhooks
All webhooks include a signature header for verification:
Node.js Verification
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expectedSignature}`)
);
}
// Express middleware
app.post('/webhooks/checkoutos', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-checkoutos-signature'];
if (!verifyWebhook(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Handle event...
res.status(200).send('OK');
});Python Verification
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(
f'sha256={expected}',
signature
)
# Flask example
@app.route('/webhooks/checkoutos', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-CheckoutOS-Signature')
if not verify_webhook(request.data, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
# Handle event...
return 'OK', 200Always Verify
Always verify webhook signatures before processing. This prevents attackers from sending fake events to your endpoint.
Retry Policy
Failed webhook deliveries are retried with exponential backoff:
Retry Schedule
{
"retry_policy": {
"max_attempts": 5,
"schedule": [
{ "attempt": 1, "delay": "immediately" },
{ "attempt": 2, "delay": "5 minutes" },
{ "attempt": 3, "delay": "30 minutes" },
{ "attempt": 4, "delay": "2 hours" },
{ "attempt": 5, "delay": "24 hours" }
],
"success_codes": [200, 201, 202, 204],
"timeout": "30 seconds"
}
}Testing Webhooks
Test your webhook endpoint before going live:
Send Test Event
curl -X POST "https://api.chargezen.com/v1/webhooks/wh_123/test" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event_type": "upsell.accepted"
}'Webhook Logs
View webhook delivery history in the dashboard or via API:
Get Webhook Logs
curl -X GET "https://api.chargezen.com/v1/webhooks/wh_123/logs" \
-H "Authorization: Bearer YOUR_API_KEY"
# Response
{
"data": [
{
"id": "log_123",
"event_id": "evt_456",
"event_type": "upsell.accepted",
"status": "delivered",
"response_code": 200,
"duration_ms": 245,
"created_at": "2025-01-16T14:30:00Z"
}
]
}Best Practices
Webhook Tips
- Respond quickly — Return 200 within 30 seconds
- Process async — Queue events for background processing
- Handle duplicates — Events may be delivered multiple times
- Verify signatures — Always validate before processing
- Monitor failures — Set up alerts for failed deliveries
Related Topics
Was this page helpful?
Need more help? Contact support