Order Syncing & Webhooks
Webhooks (notifications) are HTTP callbacks sent by Ledyer to inform your system about payment lifecycle events. Because event timing and sequence can vary based on payment methods or manual reviews, your integration must be resilient.
The Pattern: Webhooks as Triggers
The most important principle is to use webhooks only as triggers to fetch the current state, rather than relying on the data within the webhook itself.
❌ The Wrong Way
Don’t update your system using only the webhook payload. This is insecure and prone to synchronization issues.
app.post('/webhooks/ledyer', (req, res) => {
const event = req.body;
// ❌ Insecure: data could be spoofed or outdated
erp.updateOrder(event.data.orderId, { status: event.type });
res.sendStatus(200);
}); ✅ The Right Way
Use the ID from the webhook to fetch the authoritative state from Ledyer’s API.
app.post('/webhooks/ledyer', async (req, res) => {
try {
const { id, data } = req.body;
// make sure to use sessionId as backup, in some cases
// the order will remain as session for a while in Ledyer's backend
const orderId = data.orderId || data.sessionId;
// 1. Idempotency check (optional but recommended)
if (await isProcessed(id)) return res.sendStatus(200);
// 2. Fetch AUTHORITATIVE state from Ledyer API
const status = await ledyer.getPaymentStatus(orderId);
// 3. Update your system based on API data
await erp.syncOrder(orderId, status);
await markAsProcessed(id);
res.sendStatus(200);
} catch {
// Return 500 or 4xx to trigger Ledyer's retry mechanism
res.sendStatus(400);
}
}); Why This Pattern Works
- Authoritative Data: You always work with verified data from Ledyer’s API.
- Security: Spoofed webhooks only trigger a harmless API call to Ledyer.
- Resilience: Duplicate or out-of-order webhooks won’t cause issues because you always fetch the current state.
- Simplicity: You don’t need to map every single webhook event to an internal state; just sync the latest status.
Key Considerations
- Mapping: Ledyer has granular statuses (e.g.,
orderInitiated,paymentPending). You can map these to broader categories in your ERP (e.g., “Pending”). Never capture an order before it reachespaymentConfirmed. - Idempotency: Store processed webhook IDs to avoid redundant API calls and processing logic. Read more here.
- Retries: If your endpoint returns a non-200 status, Ledyer will retry the delivery. Ensure your handler is idempotent to handle these retries.
Testing Your Integration
Use the Ledyer Merchant Portal to trigger test webhooks and verify:
- Duplicates: Sending the same event twice shouldn’t cause double updates.
- Out of Order: Processing a “captured” event before a “paymentConfirmed” event should still result in the correct final state.
- API Failures: Your system should return a 5xx error if the Ledyer API is unreachable, triggering a retry.
Summary
- Extract the ID from the webhook.
- Fetch the current status from Ledyer’s API.
- Update your system using the API response.