Problem
Payment settlement errors from x402-api-host cluster at exact 4-hour intervals with 3-5 failures per burst, all from concurrent requests hitting the relay simultaneously. This pattern is consistent with a scheduled/cron job that fires multiple API calls at once.
Evidence from production logs (2026-03-25)
Error pattern — exact 4-hour intervals
| Time (UTC) |
Errors |
Error Reason |
| 01:00:12-01:00:16 |
2 |
conflicting_nonce |
| 05:00:04-05:00:11 |
4 |
conflicting_nonce |
| 09:00:04-09:00:11 |
5 |
conflicting_nonce |
| 13:00:05-13:00:08 |
4 |
conflicting_nonce |
Total: 17 errors today (up from 8 yesterday, 1 the day before, 0 the three days before that).
All errors are nonce conflicts from concurrent requests
{
"path": "/inference/openrouter/chat",
"errorReason": "conflicting_nonce",
"transaction": "",
"network": "stacks:1"
}
Paths hit: /inference/openrouter/chat and /storage/paste — both x402-protected endpoints called within the same second.
The trend is worsening
| Date |
Errors |
| Mar 19-21 |
0 |
| Mar 22 |
0 |
| Mar 23 |
1 |
| Mar 24 |
8 |
| Mar 25 |
17 |
Whatever scheduled job is calling these endpoints appears to be increasing in concurrency or frequency.
Root cause
The relay's nonce pool is designed to handle concurrent requests (atomic assignment via blockConcurrencyWhile), but when multiple settlements arrive in the same second, they each get a nonce, both broadcast, and the node rejects one with ConflictingNonceInMempool because the first transaction hasn't been indexed yet.
The relay returns NONCE_CONFLICT with retryAfter: 30, but the calling code doesn't retry — it logs the error and moves on.
Proposed fix
Option A: Serialize settlements (recommended)
If the scheduled job calls multiple x402 endpoints, serialize them — wait for each settlement to complete before starting the next:
// Instead of:
await Promise.all([
fetch("/inference/openrouter/chat", { headers: paymentHeaders }),
fetch("/storage/paste", { headers: paymentHeaders }),
]);
// Do:
await fetch("/inference/openrouter/chat", { headers: paymentHeaders });
await fetch("/storage/paste", { headers: paymentHeaders });
Option B: Add jitter between calls
If parallelism is needed for performance, add 2-5 second random jitter between calls:
for (const endpoint of endpoints) {
await fetch(endpoint, { headers: paymentHeaders });
await new Promise(r => setTimeout(r, 2000 + Math.random() * 3000));
}
Option C: Retry on NONCE_CONFLICT
This is already tracked in #84. When the relay returns retryAfter: 30 and code: "NONCE_CONFLICT", wait and retry. This would recover from the conflict without changing the call pattern.
Related
Problem
Payment settlement errors from x402-api-host cluster at exact 4-hour intervals with 3-5 failures per burst, all from concurrent requests hitting the relay simultaneously. This pattern is consistent with a scheduled/cron job that fires multiple API calls at once.
Evidence from production logs (2026-03-25)
Error pattern — exact 4-hour intervals
conflicting_nonceconflicting_nonceconflicting_nonceconflicting_nonceTotal: 17 errors today (up from 8 yesterday, 1 the day before, 0 the three days before that).
All errors are nonce conflicts from concurrent requests
{ "path": "/inference/openrouter/chat", "errorReason": "conflicting_nonce", "transaction": "", "network": "stacks:1" }Paths hit:
/inference/openrouter/chatand/storage/paste— both x402-protected endpoints called within the same second.The trend is worsening
Whatever scheduled job is calling these endpoints appears to be increasing in concurrency or frequency.
Root cause
The relay's nonce pool is designed to handle concurrent requests (atomic assignment via
blockConcurrencyWhile), but when multiple settlements arrive in the same second, they each get a nonce, both broadcast, and the node rejects one withConflictingNonceInMempoolbecause the first transaction hasn't been indexed yet.The relay returns
NONCE_CONFLICTwithretryAfter: 30, but the calling code doesn't retry — it logs the error and moves on.Proposed fix
Option A: Serialize settlements (recommended)
If the scheduled job calls multiple x402 endpoints, serialize them — wait for each settlement to complete before starting the next:
Option B: Add jitter between calls
If parallelism is needed for performance, add 2-5 second random jitter between calls:
Option C: Retry on NONCE_CONFLICT
This is already tracked in #84. When the relay returns
retryAfter: 30andcode: "NONCE_CONFLICT", wait and retry. This would recover from the conflict without changing the call pattern.Related
aibtcdev/x402-sponsor-relay#216— relay chaining limit undercounts in-flight nonces