Skip to content
Cipay
Esc
navigateopen⌘Jpreview
On this page

Troubleshooting

Diagnose common setup, catalog, checkout, account, invoice, and webhook failures.

Start with the exact symptom below. Each case includes the likely cause, checks to run, and the expected result after the fix.

The client says the key does not match the mode

Symptom: createCipayClient throws before making a network request.

Cause: a sandbox key was used with mode: "live", or a production key was used with mode: "sandbox".

const cipay = createCipayClient({
  apiKey: process.env.CIPAY_SANDBOX_API_KEY!,
  mode: "sandbox"
});

Check the key’s environment in Settings → Developer → API keys. Do not edit the key prefix. Create the correct key and replace the server secret.

Expected result: client creation succeeds and the request reads data from the intended environment.

Cipay returns an expired or revoked API key error

Symptom: the SDK throws CipayError with HTTP 401 and a code such as api_key_expired or api_key_revoked.

  1. Capture error.requestId without logging the key.
  2. Open Settings → Developer → API keys and confirm the key status and expiry.
  3. Create a replacement key with the required capabilities.
  4. Update the server secret and restart the affected application instances.
  5. Revoke the old key after the replacement is active.

Expected result: the same SDK operation succeeds with the replacement key.

The product list is empty

Symptom: products.list() or useProductsList() returns { items: [], nextCursor: null }.

Check all four values:

  • The product is Published.
  • At least one attached price is Published.
  • The API key mode and locator environment match.
  • The locator contains the current merchant ID and public slug.
const page = await cipay.products.list({
  status: "published",
  pageSize: 10
});

console.log(page.items.map(({ id, name }) => ({ id, name })));

If a search term changed, start again without the previous cursor.

Expected result: the published product appears in the first matching page.

Checkout or quote returns origin_denied

Symptom: the buyer handler returns HTTP 403 with origin_denied, or Cipay rejects the forwarded origin.

  1. In Platform, open Settings → Developer → Storefront integration.
  2. Compare the browser origin exactly, including https, hostname, and port.
  3. Add the exact origin to the correct sandbox or production storefront.
  4. Confirm your framework preserves the public request URL when passing it to the handler.

The buyer handler derives origin from the incoming request. You do not configure it separately. Express is the exception at the request-conversion boundary because a Web Request requires an absolute URL.

Expected result: a same-origin request is forwarded and Cipay accepts the configured origin; cross-origin requests remain rejected.

Checkout returned but no order appears

Symptom: the buyer reaches your return page, but no paid order or invoice is available.

The return redirect only means the browser navigated back. Check the payment flow in this order:

  1. Retrieve checkout status with the opaque capability saved for that attempt.
  2. Open Settings → Webhooks → Deliveries and find the checkout event.
  3. Confirm your receiver returned a successful status.
  4. Confirm the signed event ID was stored and processed once.
  5. Query the order after the verified event has been processed.

Expected result: the UI shows pending while processing, then shows success only after verified status or webhook processing confirms payment.

Subscriptions or orders are missing after sign-in

Symptom: account hooks stay idle, show signed-out UI, or return an empty page for a known customer.

Check both identity layers:

resolveCustomer: async (request) => {
  // This is your application-owned server auth adapter, not a Cipay helper.
  const session = await readVerifiedSession(request);
  if (!session?.cipayCustomerId) return null;
  return {
    subject: session.userId,
    customerId: session.cipayCustomerId
  };
}
  • CipayProvider should receive a non-secret sessionKey after sign-in.
  • resolveCustomer must load the verified server session and stored Cipay customer ID.
  • The mapped customer must belong to the same merchant and environment.

Expected result: the account hook runs and returns only resources owned by the mapped customer.

Cancellation fails after the page was open for a while

Symptom: subscription cancellation reports a stale revision or conflict.

Another action changed the subscription after the page loaded. Retrieve the subscription again and use its latest revision for a new confirmed action.

const current = await cipay.subscriptions.retrieve(subscriptionId);

await cipay.subscriptions.scheduleCancellation(
  current.id,
  "Customer request",
  {
    revision: current.revision,
    idempotencyKey: createIdempotencyKey("cancel")
  }
);

Do not silently retry a stale user decision against new state.

Expected result: the refreshed state is shown and the newly confirmed mutation succeeds.

Invoice download returns 404

Symptom: an authenticated buyer cannot download an invoice.

Confirm that the order ID belongs to the Cipay customer mapped from the current server session and that an invoice exists for the order. A foreign order and a missing order intentionally return the same non-enumerating response.

Expected result: the owner receives a private, non-cached PDF response; another customer learns nothing about the order.

A webhook is delivered more than once

Symptom: the same signed event ID appears in multiple delivery attempts or after manual replay.

This is expected delivery behavior. Add a unique constraint on the signed event ID and save it before business effects. Return a successful acknowledgement for duplicates without processing them again.

Expected result: the first valid delivery creates one durable job; later copies return 204 and create no additional effects.

Information to collect for support

Include enough safe context to find the request without sharing secrets:

logger.error({
  cipayRequestId: error.requestId,
  errorCode: error.code,
  httpStatus: error.status,
  sdkVersion: packageJson.dependencies["@cipay/client-sdk"],
  mode: "sandbox",
  operation: "checkout.create",
  occurredAt: new Date().toISOString()
});

Also include clear reproduction steps, the affected endpoint path without query secrets, and the webhook event or delivery ID when relevant. For UI issues, include the browser and framework version plus the visible error message.