storekit webhooks are delivered with “at least once” semantics. This means if there are issues during delivery (e.g., network problems), a webhook may occasionally be delivered more than once.
Every webhook request includes a svix-id header. This ID is:
- Unique per message - Each distinct event gets a unique ID
- Consistent across retries - The same ID is used when retrying a failed delivery
svix-id: msg_2KWPBCMzR5VXYW8xqGDKd0SLnHk
Implementing Deduplication
To ensure you only process each event once, store the svix-id and check it before processing:
app.post('/webhooks', async (req, res) => {
const svixId = req.headers['svix-id'];
// Check if we've already processed this webhook
const alreadyProcessed = await redis.get(`webhook:${svixId}`);
if (alreadyProcessed) {
return res.status(200).send('Already processed');
}
// Mark as processed (with 72-hour expiry)
await redis.set(`webhook:${svixId}`, '1', 'EX', 259200);
// Process the webhook
await processWebhook(req.body);
res.status(200).send('OK');
});
@app.route('/webhooks', methods=['POST'])
def handle_webhook():
svix_id = request.headers.get('svix-id')
# Check if already processed
if redis_client.get(f'webhook:{svix_id}'):
return 'Already processed', 200
# Mark as processed (72-hour expiry)
redis_client.setex(f'webhook:{svix_id}', 259200, '1')
# Process the webhook
process_webhook(request.json)
return 'OK', 200
When to Use Deduplication
Deduplication is especially important for:
- Payment processing - Avoid charging customers twice
- Order creation - Prevent duplicate orders
- Inventory updates - Ensure accurate stock counts
- Notification sending - Don’t spam users with duplicate messages
Storage Options
You can store processed webhook IDs in:
| Storage | Pros | Cons |
|---|
| Redis | Fast, built-in TTL | Requires Redis instance |
| Database | Already available | Slower, needs cleanup job |
| In-memory | Simplest | Lost on restart, not for distributed systems |
Set an expiry of at least 72 hours (3 days) on stored webhook IDs. Retries can occur over approximately 3 days according to the retry schedule.
Even without deduplication, designing your webhook handlers to be idempotent (producing the same result when called multiple times) is a good practice.