Skip to main content
This page covers the most common issues you might encounter with Crypto Checkout and the steps to resolve each one. If your issue is not listed here, check the delivery logs in Dashboard → Webhooks and the session detail in Dashboard → Sessions — both surfaces show the raw error messages that will point you in the right direction.
When a customer pays but the session status stays pending, the most likely cause is a misconfigured or missing Alchemy integration.Check your Alchemy integration:
  1. Open Dashboard → Integrations → Alchemy and confirm the integration is enabled.
  2. Verify that ALCHEMY_API_KEY and ALCHEMY_AUTH_TOKEN are set correctly in your environment variables.
  3. Click Provision Webhooks to register Alchemy Address Activity webhooks for each chain. This step is required after the first deploy and after adding new chains.
Verify the deposit address is monitored:When a session is created, Crypto Checkout automatically adds the derived deposit address to Alchemy’s address activity list. If provisioning failed (e.g., the Alchemy token was missing at deploy time), the address may not be registered.Use the Check payment button on the session detail page to trigger an immediate on-chain scan. This bypasses Alchemy and calls the chain directly — it is the fastest way to manually recover a session that should be paid.Confirm the customer paid to the correct address:Ask the customer to share their transaction hash. Look it up on the relevant block explorer and verify:
  • The to address matches the session’s address field exactly
  • The token and chain match what was requested
  • The transaction has reached the required number of confirmations (default: 3)
The underpaid status means the customer sent less than the required amount. The session does not auto-resolve — it stays in underpaid regardless of elapsed time.Resolution options:
  • Contact the customer. They may be able to send a top-up transaction to the same deposit address, but note that the session may have already expired.
  • If the session has expired, create a new session for the remaining difference and redirect the customer to it.
  • If the payment amount was incorrect on your side (e.g., a currency conversion issue), void the old session and create a new one for the correct amount.
The underpaid status is a terminal state by design — it requires a merchant decision. Crypto Checkout does not automatically credit partial payments because doing so could enable payment griefing attacks.
The default session TTL is 5 minutes. If a customer pays after the session expires, the following behaviour applies:
  • Within the grace window (default: 10 minutes): the session transitions to paid_late. Treat this as a successful payment — the funds were received.
  • After the grace window: the session is permanently expired. The on-chain transaction exists but is not automatically linked to the session.
Manual recovery after the grace window:
  1. Open the session in Dashboard → Sessions.
  2. Use the Check payment button to force an on-chain scan. Even for expired sessions, this can detect and link a late payment.
  3. If the scan confirms the funds arrived, fulfil the order manually and update your internal records.
You can increase the session TTL (default: 5 minutes) and the late payment grace window (default: 10 minutes) in your environment to give customers more time to complete payment — useful for high-value transactions or customers unfamiliar with crypto wallets.
A 401 response means the API could not authenticate your request.Common causes and fixes:
  • Missing header — ensure you are sending Authorization: Bearer ck_... on every request. The Bearer prefix and a single space are required.
  • Invalid key — the key may have been copied incorrectly. Raw keys are only shown once at creation time. If in doubt, revoke the key and generate a new one in Dashboard → API Keys.
  • Revoked key — a key cannot be used after revocation. Check Dashboard → API Keys for a Revoked badge next to the key prefix.
  • Wrong environment — make sure you are using a ck_test_... key against your development environment and a ck_live_... key in production. The mode is baked into the key prefix.
# Verify your key is being sent correctly
curl -X GET https://your-domain.com/api/v1/checkout_sessions \
  -H "Authorization: Bearer ck_test_YOUR_KEY"
A 403 response means the API key is valid but does not have the required scope for the operation you are attempting.Available scopes:
ScopeGrants access to
sessions:readRetrieve and list checkout sessions
sessions:writeCreate checkout sessions
links:readRetrieve and list payment links
links:writeCreate, update, and delete payment links
webhooks:readRetrieve and list webhook endpoints
webhooks:writeCreate, delete, and test webhook endpoints
events:readRetrieve and list events
customers:readRetrieve and list customers
customers:writeCreate and update customers
Fix: Generate a new API key in Dashboard → API Keys with the correct scopes selected. You cannot add scopes to an existing key — create a new one and revoke the old key once you have updated your environment.
If session.paid events are not reaching your endpoint, work through this checklist:1. Confirm the endpoint URL is publicly reachable. Your Crypto Checkout server must be able to open an outbound HTTPS connection to your webhook URL. If you are developing locally, use ngrok http 8256 or cloudflared tunnel to expose your dev server.2. Check the delivery log. Open Dashboard → Webhooks → [your endpoint] → Delivery log. Each attempt shows the HTTP status code, response body, and error message returned by your server.3. Understand the retry schedule. Failed deliveries are retried up to 6 times over approximately 31 hours using exponential backoff. If all 6 attempts fail, the delivery is marked failed and no further retries occur.4. Check your endpoint’s response. Your handler must return a 2xx status code within the timeout window. Any other status code (including 3xx redirects) counts as a failure. Ensure your handler calls res.sendStatus(200) (or equivalent) before performing any slow operations.5. Send a test event. Use the Test button on the endpoint detail page (or the API) to send a synthetic session.paid event. This is the fastest way to verify your handler is reachable and responding correctly:
curl -X POST https://your-domain.com/api/v1/webhook_endpoints/{id}/test \
  -H "Authorization: Bearer ck_test_YOUR_KEY"
A sweep failure means Crypto Checkout received the payment but could not forward the funds to your payout wallet. Check Dashboard → Payouts for the specific error message.Common causes:
  • Insufficient gas wallet balance — for EVM chains, the gas wallet (the wallet that signs the sweep transaction) needs ETH (or POL on Polygon) to pay gas fees. Add a small amount of the native token to cover gas.
  • ERC-20 token sweeps — sweeping USDC, USDT, or other ERC-20 tokens requires the gas wallet to hold ETH (or the chain’s native token) even though the asset being swept is an ERC-20. Ensure the gas wallet has enough native token to cover the token approval and transfer gas.
  • RPC error — a transient failure from the Alchemy RPC node. The sweep will be retried automatically; check Dashboard → Payouts again after a few minutes.
  • Wrong payout address format — verify your payout addresses in Settings → Wallet. EVM addresses must be valid checksummed hex; Solana addresses must be valid base58 public keys.
If a sweep fails and is not automatically retried, use the Retry sweep button on the payout detail page. Do not manually send funds — always use the dashboard controls to avoid double-sending.
Webhook signature verification failures are almost always caused by one of these issues:1. Not using the raw request body. You must compute the HMAC over the raw bytes received from the network — do not JSON-parse and re-serialize the body, and do not let a body-parser middleware decode it first.
// Correct: raw body middleware BEFORE JSON.parse
app.post("/webhooks/crypto", express.raw({ type: "*/*" }), (req, res) => {
  const rawBody = req.body.toString(); // raw string, not JSON.parse(req.body)
  const event = verifyWebhook({ payload: rawBody, ... });
});
2. Using the wrong secret. The secret for signature verification is the webhook endpoint secret — the value shown once when you create the endpoint in Dashboard → Webhooks. It is not your API key. If you lost the secret, delete the endpoint and create a new one.3. Stale timestamp. Deliveries more than 5 minutes old are rejected to protect against replay attacks. Ensure your server clock is synchronised (NTP) and that you are not buffering or delaying webhook processing.4. Accidental body re-encoding. Some frameworks (e.g., AWS API Gateway, certain reverse proxies) may base64-encode or otherwise transform the request body. If you are behind such a proxy, decode the body back to its original byte representation before verifying.Manual verification (Node.js, no SDK):
const [tsPart, v1Part] = req.headers["x-webhook-signature"].split(",");
const ts = tsPart.split("=")[1];
const v1 = v1Part.split("=")[1];

const expected = crypto
  .createHmac("sha256", process.env.CHECKOUT_WEBHOOK_SECRET)
  .update(`${ts}.${rawBody}`)
  .digest("hex");

if (!crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(v1, "hex"))) {
  throw new Error("Invalid signature");
}
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
  throw new Error("Timestamp too old");
}

Additional resources

Signature verification

Full reference for HMAC signature verification, including test vectors.

Payouts

Understanding sweep transactions and payout statuses.

Session lifecycle

Every session status and the conditions that trigger each transition.