Runbook: Crypto Payment Incidents
This runbook covers the manual procedures for crypto payment edge cases that require human intervention. Automated handling covers the happy path; the scenarios below are ones that fall out of automation.
Partial Payment (partially_paid)
What happened
The customer sent less crypto than the invoice required. This can happen because:
- The customer deducted network/gas fees from the sent amount.
- Exchange rate moved between invoice creation and payment.
- The customer fat-fingered the amount.
Signals
- Outbox kind:
nowpayments_partial_payment - Ledgerline admin notification email (sent via outbox
email_send) - No order confirmation has been sent to the customer
Decision tree
-
Check the shortfall. Pull the outbox message payload (
paymentId,paidAmount,expectedAmount,payCurrency). Calculate shortfall:expectedAmount - paidAmount. -
If shortfall < $1.00 USD equivalent → this is almost certainly a rounding/fee artifact. Manually mark the order paid in AdminJS (open the order, set status to
paid_pending_fulfillment). Log the action in the Decisions log. -
If shortfall >= $1.00 → contact the customer. Options:
- Ask them to send the remaining amount to the same address (NOWPayments will accept it if the invoice is still open).
- Issue a partial refund and cancel the order.
- Issue store credit for the paid amount.
-
If the invoice has expired → the payment window is closed. Contact the customer and offer a new invoice or refund via their original payment method.
Actions
- Mark order paid (AdminJS): Orders → find order by
orderRef→ change status. - Issue refund: NOWPayments does not support automated refunds. Contact NOWPayments support or manually transfer the customer's crypto back from the NOWPayments dashboard.
Failed / Expired Payment
What happened
Payment was not received before the invoice expired, or NOWPayments explicitly marked it failed.
Signals
- Outbox kind:
nowpayments_mark_failed - Order remains in
payment_pendingstatus.
Procedure
- Confirm the order is in
payment_pendingstatus (AdminJS or DB). - Check if the customer should be notified — the outbox
nowpayments_mark_failedhandler should have sent a notification. If not, send one manually. - The inventory was not released at this point. Once the order status transitions to
failed, inventory should be released. Confirm this happened by checking inventory levels. - If the customer contacts support wanting to retry, create a new invoice via the store checkout flow.
Webhook Not Being Received
Symptoms
Payment shows as completed on NOWPayments dashboard but Ledgerline did not update the order.
Checklist
- Verify IPN URL is set in NOWPayments Dashboard → IPN Settings → Callback URL. Should be:
https://<your-render-url>/api/nowpayments/webhook - Check Render logs for
POST /api/nowpayments/webhookentries around the time of the payment. - Check for 401 responses: These mean the HMAC-SHA512 signature failed. Verify
NOWPAYMENTS_IPN_SECRETin Render matches the IPN secret in the NOWPayments dashboard exactly (no trailing spaces). - Check for 500 responses: Could mean
NOWPAYMENTS_IPN_SECRETis not set (andIS_PRODUCTION=true), or the outbox enqueue failed. - Replay from NOWPayments: The NOWPayments dashboard lets you re-send IPN notifications. Use this to trigger a replay once the issue is resolved.
Manual payment marking
If the webhook cannot be replayed or is taking too long:
- In AdminJS, find the order by
orderRef(theorder_idfrom the NOWPayments payment). - Verify the payment on the NOWPayments dashboard (payment_id, actually_paid vs pay_amount).
- Manually set order status to
paid_pending_fulfillment. - Log the manual action and the NOWPayments payment_id in the order notes.
Signature Verification Failures (401 flood)
Symptoms
Render logs show a flood of nowpayments-webhook: rejected — invalid HMAC-SHA512 signature at 401.
Possible causes
- IPN secret mismatch — the most common cause. Compare
NOWPAYMENTS_IPN_SECRETin Render with the current value in NOWPayments Dashboard. - NOWPayments secret rotation — if someone regenerated the IPN secret in the dashboard, update Render immediately.
- Scan / probe traffic — attackers probing the webhook endpoint. The rate limiter (default 120 req/min) will throttle these.
Resolution
- Regenerate the IPN secret in NOWPayments if compromised.
- Update
NOWPAYMENTS_IPN_SECRETin Render and redeploy. - Verify IPN delivery resumes by checking Render logs.
Currency No Longer Available
Symptoms
listAvailableCurrencies() returns a shorter list than expected. A coin the tenant was using is no longer in the response.
Cause
NOWPayments occasionally suspends coins for maintenance or regulatory reasons.
Procedure
- Verify on the NOWPayments status page or dashboard.
- If the coin is temporarily suspended: the existing invoices may still be payable once the coin is re-enabled. Monitor and re-enable in Ledgerline config when available.
- If the coin is permanently removed: update tenant configuration to remove it from the supported payment methods list. Notify affected customers.
- The currency cache TTL is 24 h. Once the coin comes back, wait up to 24 h or manually clear the Valkey key
nowpayments:currencies:v1to force a refresh.
Clearing the currency cache
# Via Render shell or local dev with VALKEY_URL set:
redis-cli -u $VALKEY_URL DEL nowpayments:currencies:v1