DEV Community

Daniel Ioni
Daniel Ioni

Posted on

MyZubsterGateway: Building a Reliable Webhook System with Exponential Backoff

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

  1. 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() });
});

  1. 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.

  1. 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.

  1. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Links & Resources

GitHub Repository: DanielIoni-creator/MyZubsterGateway

Live Demo: https://myzubster.com

Dev.to Profile: @danielioni
Enter fullscreen mode Exit fullscreen mode

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)