Cryptomus USDT top-up not credited on monkeytheme.com.

A real diagnosis flow for orders that stay PENDING, jump to CANCELLED, or never reach your balance after a successful Cryptomus pay_url load. Built from production webhook logs and the payment_orders schema on monkeytheme.com.

/api/v1/payment/
webhook/cryptomus
Only real endpoint
400 missing signProbe, not a real top-up
30 minutesDefault order expiry
4 pathsSeen in 7 days of logs

What you actually need to check, in order

If a Cryptomus USDT top-up looks stuck, work through these four checks before opening a ticket. Each one has a concrete answer you can verify yourself.

1. Did the order reach payment_orders?

Sign in, open /dashboard/orders, and find the order id. If the row does not exist at all, the create-order call never reached the backend, which usually means the request was made before you were signed in, or the same key was reused across browsers.

If the row exists, look at status, paid_at and expires_at:

statusWhat it usually meansNext action
PENDINGUser opened pay_url but did not finish the on-chain transfer, or the webhook has not landed yet.Wait up to 30 minutes, then check again. Cryptomus retries webhooks.
COMPLETEDWebhook validated, balance was credited, order is closed.Nothing to do. If balance did not move, file a ticket with the order id.
CANCELLEDUser closed pay_url without paying, or the order expired.Create a fresh order. Old one is not recoverable.
EXPIREDHit the ORDER_TIMEOUT_MINUTES (default 30) without a webhook.Create a fresh order. If this repeats, the upstream webhook is being lost.
FAILEDWebhook arrived but the upstream reported failure.File a ticket with the order id, paid_at and the cryptomus invoice id.

2. Did Cryptomus POST to the official webhook?

The frontend bundle on monkeytheme.com (providerConfig-*.js) hard-codes one and only one notifyUrl:

cryptomus:"/api/v1/payment/webhook/cryptomus"

Anything else is wrong. From seven days of production logs we counted four distinct webhook URLs being posted to monkeytheme.com, and three of them are noise:

Path seenStatusSource
/api/v1/payment/webhook/cryptomus200 on real webhooks, 400 on probesOfficial frontend
/api/v1/payment/cryptomus/webhook404Probe (path-order swap)
/api/v1/payment/webhooks/cryptomus404Probe (extra s)
/api/cryptomus/webhook404Probe (version-less)

The 400s on the official path are all cryptomus webhook missing sign with bodyLen: 2, which means an empty {} body. That is not a real top-up, that is a scanner or a misconfigured client. A real Cryptomus webhook body always carries a JSON sign field plus the order payload.

3. Did the user complete the on-chain transfer?

Cryptomus hosted page will show a success state once the on-chain transfer reaches the required confirmations. Until that happens, Cryptomus will not POST the webhook. If the user closed the tab before paying, the order is still PENDING and will time out at expires_at.

You can also ask the user to copy the cryptomus invoice id from the hosted page URL (it looks like pay_url?...&orderid=... or the order id shown after redirect) and check it directly in the Cryptomus merchant dashboard under Payments.

4. Did the returnUrl poll finish?

After a successful on-chain payment, the hosted page redirects to /payment/result. The frontend polls GET /api/v1/payment/orders/my for a few seconds to detect the COMPLETED state. If the user closes that tab too early, the balance is still credited by the webhook, but the UI keeps saying PENDING until the next page load.

Fix: refresh the orders page. Do not assume the webhook is broken just because the popup closed early.

The real flow, end to end

How a USDT top-up actually moves from /purchase to a credited balance on monkeytheme.com:

1. Create orderPOST /api/v1/payment/orders
status=PENDING, expires_at = now + ORDER_TIMEOUT_MINUTES
2. Hosted pay_urlUser pays in Cryptomus UI
on-chain transfer waits for confirmations
3. WebhookPOST /api/v1/payment/webhook/cryptomus
validates sign, idempotency, amount, currency
4. Credit balancesub2api updates wallet + status=COMPLETED
paid_at is set, redeem is granted
5. Return pollGET /api/v1/payment/orders/my
UI flips to paid, popup auto-closes

Step 3 is the only step where a server-side misconfiguration can silently drop a paid order. Step 4 happens inside the same webhook handler, so if step 3 returns 200, step 4 has run. If you see a 400 on step 3, step 4 never started and the user is not credited yet.

What the logs look like in practice

A real webhook log line on monkeytheme.com looks like this. Source: journalctl -u sub2api --since "7 days ago", captured 2026-08-08.

INFO  middleware/logger.go:82  http request completed
  service=sub2api  env=production  component=http
  request_id=f55564df-948e-4847-8a17-d72285534880
  path=/api/v1/payment/webhook/cryptomus
  method=POST
  status_code=400
  latency_ms=7
  client_ip=65.49.151.58
  protocol=HTTP/1.1

Right above it, in the same transaction:

ERROR slog logger/slog_handler.go:53  [Payment Webhook] verify failed
  provider=cryptomus
  error="cryptomus webhook missing sign"
  method=POST
  bodyLen=2

Two things to take from this:

Be careful: client_ip 65.49.151.58 and 2a02:4780:10:6b04::1 are not real users. They show up across multiple unrelated endpoints (/api/cryptomus/webhook, /api/auth/google, /api/v1/settings/public) in the same minute. If your log shows the same pattern, you are looking at a probe, not a customer.

If you have to escalate

Open a ticket only after the four checks above. Send these four pieces of information so support can act on the first reply:

  1. Order id (number from /dashboard/orders).
  2. Cryptomus invoice id (from the hosted page URL or merchant dashboard).
  3. Status and paid_at from the row you already pulled.
  4. A 5-minute log slice: journalctl -u sub2api --since "5 minutes ago" --no-pager | grep -E "orderid=YOUR_ORDER_ID|payment/webhook/cryptomus|verify failed"

Do not send screenshots of the wallet address. Do not send your account password. Do not paste the API key. The order id and invoice id are enough to look up the row.

FAQ

What is the official Cryptomus webhook URL on monkeytheme.com?

/api/v1/payment/webhook/cryptomus. Other paths such as /api/v1/payment/cryptomus/webhook, /api/v1/payment/webhooks/cryptomus (with the extra s) and /api/cryptomus/webhook are not registered routes; they return 404 even though production logs sometimes see requests hit them.

Why does the official webhook keep returning 400 with 'cryptomus webhook missing sign'?

It means the body Cryptomus posted did not include a sign field, or the body was empty. From the production log on 2026-08-02 the failing POSTs were all bodyLen=2 (literally {}). Real Cryptomus webhooks always carry a JSON body with a sign field, so an empty body is not a real top-up, it is either a probe, a misconfigured client, or a proxy that strips the body.

If the order stays PENDING for hours, is the webhook broken?

Not necessarily. The webhook path is only one half of the flow. Cryptomus usually retries the webhook when the first attempt fails; it also offers a manual resend from the merchant dashboard. The other half is the returnUrl hop: after paying, the user is bounced back to /payment/result, which the frontend polls for status. If the user closes the tab before the return hop, the order may sit PENDING until expiry (default 30 minutes via ORDER_TIMEOUT_MINUTES).

What this guide does not cover

This page is about the post-payment side: did the webhook arrive, did the order move to COMPLETED, is the balance credited. It is not about choosing a wallet, picking a network, or estimating gas. Those have their own decisions and their own risks; the upstream changes them faster than we can keep docs in sync, so link to Cryptomus docs instead of copying numbers that go stale.