← All Posts
Tutorials

Idempotent Webhook Handling: Never Process a Payment Twice

May 21, 2026· 2 min read

Why Duplicates Happen

Paychainly retries webhook delivery if your endpoint returns a non-2xx status or times out. Network blips mean the same deposit_detected event may arrive 2–3 times. Your handler must be safe to call multiple times.

The txHash is Your Idempotency Key

Every payment event carries a unique txHash — the on-chain transaction hash. Use it as a natural idempotency key:

CREATE TABLE processed_payments (
  tx_hash VARCHAR(66) PRIMARY KEY,
  user_id INTEGER NOT NULL,
  amount DECIMAL(18,6) NOT NULL,
  processed_at TIMESTAMP DEFAULT NOW()
);

Node.js Handler Pattern

app.post('/webhooks/paychainly', async (req, res) => {
  // 1. Verify signature first
  if (!verifySignature(req.rawBody, req.headers['x-paychainly-signature'], WEBHOOK_SECRET)) {
    return res.status(401).end();
  }

  const { txHash, amount, userId } = req.body;

  // 2. Idempotency check
  const [record, created] = await ProcessedPayment.findOrCreate({
    where: { txHash },
    defaults: { userId, amount },
  });

  if (!created) {
    // Already processed — safe to acknowledge
    return res.status(200).json({ status: 'already_processed' });
  }

  // 3. Business logic — runs exactly once per txHash
  await creditUserAccount(userId, amount);
  await sendConfirmationEmail(userId);

  res.status(200).json({ status: 'ok' });
});

Database-Level Guarantee

The PRIMARY KEY on tx_hash gives you a database-level guarantee. Even under concurrent requests, only one insert succeeds — the other gets a unique constraint error which you can catch and handle as "already processed".

Timeout Considerations

Paychainly's webhook timeout is 25 seconds (WEBHOOK_FETCH_TIMEOUT_MS). If your handler takes longer, queue the work and acknowledge immediately:

// Acknowledge first, process async
res.status(200).json({ status: 'queued' });
await jobQueue.add('process-payment', { txHash, amount, userId });
← Back to Blog
idempotencywebhooksduplicate preventiontxHashpayment processing