MyZubsterGateway: Building a Reliable Webhook System with Exponential Backoff
A deep dive into why we added a health check, a retry mechanism, and how we solved the MongoDB puzzle.
The Context: Why This Matters
MyZubster is an open-source, privacy-first ecosystem for peer-to-peer services, powered by Monero payments and Tor anonymity. At its heart lies the MyZubsterGateway – the engine that handles all Monero interactions: generating unique subaddresses for each order, monitoring payments, and sending webhook notifications when payments are confirmed.
But there was a gap. In a production environment, reliability isn't optional. If a webhook fails – because the merchant's server is temporarily down, or there's a network hiccup – the order status might never update. That's a problem. Today, we solved it.
What We Built
- Health Check Endpoint
We added a simple but essential GET /api/health endpoint to monitor the gateway's status. This is the first line of defense in any observability strategy – a quick ping to know if the service is alive.
javascript
// In server.js
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
- Webhook Retry Mechanism with Exponential Backoff
This is the main feature. We built a WebhookService that automatically retries failed webhook deliveries using an exponential backoff strategy. Instead of a naive "try once and fail" approach, the service schedules retries with progressively longer delays:
javascript
// services/webhookService.js
class WebhookService {
constructor() {
this.retryDelays = [5, 30, 120, 600]; // 5s, 30s, 2m, 10m
this.maxRetries = this.retryDelays.length;
}
async sendWebhook(url, payload, retryCount = 0) {
// Attempt delivery with timeout and status code validation
// On failure, schedule next retry with exponential backoff
}
}
Why exponential backoff? If a webhook fails due to a temporary issue (like a server restart), retrying immediately won't help. By waiting progressively longer, we give the downstream service time to recover without overwhelming it with requests.
- Integration with PaymentMonitor
The PaymentMonitor now uses the WebhookService when a payment is confirmed:
javascript
// In paymentMonitor.js
if (result.status === 'confirmed') {
const webhookResult = await WebhookService.sendWebhookAsync(
tx.webhookUrl,
{
orderId: tx.orderId,
amount: tx.amount,
status: 'confirmed',
txHash: result.txHash
}
);
}
If the webhook fails permanently (after all retries), the order is marked accordingly – so we never lose track of what happened.
- Test Endpoint
We added a test endpoint POST /api/test-webhook so you can validate the retry logic without waiting for a real payment:
bash
curl -X POST http://localhost:3002/api/test-webhook \
-H "Content-Type: application/json" \
-d '{"targetUrl":"https://your-webhook-endpoint","payload":{"test":true}}'
The "Why": Reliability in Payment Systems
In a payment gateway, webhook delivery isn't optional. When a user pays for a service, the marketplace needs to know immediately so it can release the order, notify the seller, and move forward. If the webhook fails:
❌ The seller might not receive the order confirmation
❌ The buyer might not get access to the service
❌ The platform loses trust
With the retry mechanism, the gateway now guarantees delivery – eventually. Even if the merchant's server is down for a few minutes, the webhook will still be delivered once it comes back online.
The Challenge We Overcame: MongoDB
During development, we hit a classic infrastructure issue: MongoDB wouldn't start. The logs showed:
text
Writing to log file failed, aborting application
The fix was straightforward once we diagnosed it:
bash
mkdir -p /var/log/mongodb /var/lib/mongodb
chown -R mongodb:mongodb /var/log/mongodb /var/lib/mongodb
chmod 755 /var/log/mongodb /var/lib/mongodb
systemctl restart mongod
Lesson learned: always check file permissions and directory ownership. A small oversight can block the entire system.
The Code in Action
Here's the full flow:
User places an order → Gateway generates a unique Monero subaddress
User sends Monero to that subaddress
PaymentMonitor detects the transaction (every 30 seconds)
WebhookService attempts to deliver the confirmation
If it fails → retry with backoff (5s → 30s → 2m → 10m)
If all retries fail → order marked as webhookFailed for manual review
What's Next?
This is just the beginning. The next steps on the roadmap include:
Mainnet support for Monero (currently in testnet)
NFC payments for the mobile app
Advanced security bot with Kali Linux and DeepSeek integration
Links & Resources
GitHub Repository: DanielIoni-creator/MyZubsterGateway
Live Demo: https://myzubster.com
Dev.to Profile: @danielioni
Final Thoughts
Building a payment gateway is about more than just moving money. It's about trust, reliability, and privacy. Today's work on webhook retries might seem like a small detail, but in production, it's the difference between a system that works and one that fails silently.
Every webhook matters. Every order counts. And now, MyZubsterGateway makes sure neither is lost.
If you're building something similar, I'd love to hear about your approach to webhook reliability. Drop a comment below!
Top comments (0)