Does Clip Have a Sandbox? Test Cards, Test Credentials, and the Production-Charge Playbook (2026) — Cesar Ayala
← All posts

Does Clip Have a Sandbox? Test Cards, Test Credentials, and the Production-Charge Playbook (2026)

Partly. Clip's reference docs now provide test credentials and test cards, but only for the Transparent Checkout and Refunds APIs. Its FAQ still says there's no sandbox, and everything else (payment links, terminals, other APIs) has none. For those you test in production with a tiny real charge (banks accept MXN$1 or even $0.01), then refund it.

Does Clip have a sandbox? The verified 2026 answer (and why the docs disagree)

Partly. As of July 2026, Clip ships test credentials and test cards — but only for its Transparent Checkout and Refunds APIs. For everything else (payment links, terminals, any other endpoint) there is no sandbox: you test in production with a tiny real charge you immediately refund.

That partial coverage is the whole story, because Clip’s own docs disagree with each other. The reference page at developer.clip.mx/reference/pruebas now publishes those test credentials, while the FAQ at developer.clip.mx/page/preguntas-frecuentes still says the opposite: “No tenemos sandbox ni tarjetas de prueba, pero puedes realizar pruebas haciendo transacciones por montos pequeños.” Both are true at once. If you only read the FAQ, you’ll needlessly charge real cents to test a flow that has a real sandbox. If you only read the reference page, you’ll assume a sandbox exists for a flow that doesn’t. Below is how to tell which world you’re in, and the production-testing ritual for when you’re in the second one.

What Clip’s test credentials actually cover: Transparent Checkout + Refunds only

The test mode on /reference/pruebas is scoped. It covers the Transparent Checkout endpoints — card tokenization (generatecardtoken), the payment call (realizarpago, i.e. POST /payments), payment methods (obtenermetodosdepago), and monthly installments / meses sin intereses (obtenercuotasmensuales) — plus the Refunds API. It also covers the Transparent Checkout SDK. Anything outside that list has no test mode at all.

Checkout TransparenteTest creds + test cards ✓
Refunds APITest creds ✓
Payment links (redirect)No sandbox — prod only
Terminals / everything elseNo sandbox — prod only

So the practical rule: if you’re building Checkout Transparente (customer stays on your site, you tokenize the card and charge via API), use the real test credentials. If you’re building Checkout Redireccionado (a payment link that redirects to Clip’s hosted page) or Checkout Embebido, or you’re touching terminals, there is no sandbox and you go straight to the production ritual later in this post. This is the same three-mode split covered in the sibling Integrate Clip payments guide.

The gotcha: Clip does not give you a separate sandbox host or a mode flag. Test versus live is decided entirely by which credential you send, and the two credentials do not even look alike. Test credentials are Bearer tokens with a test_ prefix that the API auto-detects as sandbox mode. Production credentials are your API key and secret Base64-encoded behind Basic, sent in x-api-key or authorization depending on the API. You can create up to 6 test credentials and 6 production credentials per account. Same base URL, same request body — but the auth header changes shape between test and live.

# TEST — a Bearer credential with the test_ prefix routes you to the sandbox
curl https://api.payclip.com/payments \
  -H "Authorization: Bearer test_6e107925-dbe0-479e-a8b8-0d93ced3c502" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 1.00, "currency": "MXN", "payment_method": { "token": "..." } }'

# LIVE — a production credential is Basic (base64 of key:secret) at the same endpoint
curl https://api.payclip.com/payments \
  -H "Authorization: Basic <base64-of-your-live-key:secret>" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 1.00, "currency": "MXN", "payment_method": { "token": "..." } }'

The danger is obvious once you name it: because the endpoint is identical, a single wrong environment variable turns every “test” into a real charge. Verify the exact Authorization header for the API you’re calling against developer.clip.mx/reference — Clip sends the credential in x-api-key or authorization depending on the endpoint — and treat the test_ prefix as a hard invariant: assert on it in code before you ever call realizarpago. We’ll wire that guard in below.

Testing the covered flow: test credentials, the test-card table, and the 15-minute card token

For Checkout Transparente, the flow is two calls. First generatecardtoken returns a Card Token ID that expires in 15 minutes; then realizarpago charges that token. In test mode you feed it one of Clip’s published test cards (any future expiry, any CVV):

Brand Test PAN (MX, paid)
Visa 4766944332216006
Mastercard 5215956400364553
Amex 377770358335399

Each card also has variants that force scenarios like insufficient funds or a restricted card — check the reference page for the full matrix. The 15-minute expiry matters in tests too: if your suite tokenizes, then does other work, then charges, a slow run can let the token expire between the two calls. Tokenize immediately before charging.

// Node — the two-call Checkout Transparente flow, in TEST mode
const CLIP_KEY = process.env.CLIP_KEY; // test_... in sandbox
const auth = { Authorization: `Bearer ${CLIP_KEY}`, "Content-Type": "application/json" };

async function chargeTestCard() {
  // 1) generatecardtoken -> Card Token ID in `id` (expires in 15 min)
  const tokenRes = await fetch("https://api-secure.payclip.com/card_tokens", {
    method: "POST",
    headers: auth,
    body: JSON.stringify({
      card_number: "4766944332216006",
      expiration_month: "12",
      expiration_year: "2030",
      cvv: "123",
    }),
  });
  const { id: cardToken } = await tokenRes.json();

  // 2) realizarpago -> charge the token right away (nested under payment_method)
  const payRes = await fetch("https://api.payclip.com/payments", {
    method: "POST",
    headers: auth,
    body: JSON.stringify({
      amount: 1.0,
      currency: "MXN",
      payment_method: { token: cardToken },
    }),
  });
  return payRes.json();
}

Confirm the exact request bodies and field names against developer.clip.mx/reference — treat the shapes above as the flow, not the contract.

Why Clip is not Stripe or Conekta: no universal test mode

Stripe and Conekta give you one coherent test mode that spans essentially every product — cards, OXXO, SPEI, webhooks, the lot — with dedicated test keys that never touch real money. If you’ve built on those, see how the test story compares in payment gateways in Mexico and the Conekta integration guide. Clip is different: its sandbox is a feature of two specific APIs, not a global environment.

Stripe / Conekta

  • One test mode across every product
  • Test keys never charge real money
  • Test webhooks and test events built in
  • Same mental model everywhere

Clip (2026)

  • Test creds only: Checkout Transparente + Refunds
  • No sandbox for links, terminals, other APIs
  • Test vs live chosen by key prefix, same URL
  • Everything else: real charge, then refund

The takeaway: don’t carry a Stripe mental model into a Clip build. Ask, per flow, “does this specific API have test credentials?” If no, you’re doing production testing — deliberately, with guardrails.

The production-testing ritual for everything else: charge a cent, then refund it

For any Clip flow without a sandbox, the FAQ’s own guidance is the playbook: make a real charge for a tiny amount — MXN$1, or even MXN$0.01, where commission is practically null — then refund it. A successful real charge is actually a stronger signal than a sandbox pass: it proves you’re genuinely production-ready, credentials and all. The discipline is what keeps it safe.

// Node — the "charge a cent, then refund it" production ritual
// Production auth is Basic: base64("<api-key>:<secret>"). Build it once, from env.
const CLIP_LIVE_AUTH = `Basic ${Buffer.from(process.env.CLIP_LIVE_CREDENTIAL).toString("base64")}`;
const auth = { Authorization: CLIP_LIVE_AUTH, "Content-Type": "application/json" };

async function productionSmokeTest(cardToken, idempotencyKey) {
  // Charge MXN$0.01 with an idempotency key (see next section)
  const chargeRes = await fetch("https://api.payclip.com/payments", {
    method: "POST",
    headers: { ...auth, "Idempotency-Key": idempotencyKey },
    body: JSON.stringify({
      amount: 0.01,
      currency: "MXN",
      payment_method: { token: cardToken },
    }),
  });
  const charge = await chargeRes.json();

  // Immediately refund it via the Refunds API (verify the exact path/fields in the reference)
  await fetch("https://api.payclip.com/refunds", {
    method: "POST",
    headers: auth,
    body: JSON.stringify({ payment_id: charge.id, amount: 0.01, currency: "MXN" }),
  });

  return charge.id;
}

Refund promptly and log the pair. If you invoice these test charges, remember the refund needs its own CFDI de Egreso — see CFDI de Egreso / refunds.

Idempotency Clip doesn’t give you: enforce a dedupe key so a retried QA run never double-charges

The single biggest risk in production testing is a retry. A flaky network, a re-run of your QA suite, a doubled button click — and you’ve charged the customer’s card twice with real money. The fix is an idempotency key: a stable, deterministic key per logical charge so a retried call returns the original result instead of creating a second one. This is standard gateway hygiene; the same replay/idempotency reasoning is spelled out in webhooks vs polling vs API and the Stripe signature verification post.

import { createHash } from "node:crypto";

// Deterministic key: same logical charge -> same key -> at most one charge
function idempotencyKey(orderId: string, amountMxn: number): string {
  return createHash("sha256").update(`clip:${orderId}:${amountMxn}`).digest("hex");
}

Confirm the exact idempotency header name Clip honors on developer.clip.mx/reference; if the API doesn’t dedupe server-side for a given flow, enforce the key in your own store — record the key before you call, refuse to re-charge if it’s already present, and make your QA runner reuse one key per test so re-runs are no-ops.

Guarding production: feature-flag the amount, hard-cap it, log every test charge

Production testing is only safe with rails. Three of them. Feature-flag the test path so it can’t run by accident. Hard-cap the test amount so a bug can never turn a “cent” into a large charge. And log every test charge with its idempotency key and refund so reconciliation is trivial.

const MAX_TEST_MXN = 1.0; // MXN$1.00 ceiling — refuse anything larger

function assertTestCharge(amountMxn: number, key: string) {
  if (process.env.ALLOW_PROD_TEST !== "true") {
    throw new Error("Prod test charges are flag-gated; set ALLOW_PROD_TEST=true");
  }
  if (amountMxn > MAX_TEST_MXN) {
    throw new Error(`Test charge MXN$${amountMxn} exceeds cap MXN$${MAX_TEST_MXN}`);
  }
  console.log(JSON.stringify({ evt: "clip_prod_test", amountMxn, key, at: Date.now() }));
}
  1. Flag-gateTest path runs only when ALLOW_PROD_TEST=true
  2. Cap the amountReject anything over the MXN$1 ceiling
  3. Charge with idempotency keyA retry returns the original, never a second charge
  4. Refund immediatelyCall the Refunds API right after the charge
  5. Log the pairCharge id + refund id + key for reconciliation

Webhook and reconcile: verify the notification, then fetch the truth from Get Payment

Clip sends a webhook when a payment’s status changes. A verified notification looks like this:

{
  "id": "pinpad-...",
  "origin": "pinpad-payments-api",
  "event_type": "PINPAD_INTENT_STATUS_CHANGED"
}

Treat that as a signal, not the truth — for a test charge or a real one. The webhook tells you something changed; it does not carry the authoritative final state, and you should not derive a status from the event_type string. So verify the notification’s authenticity first, then call the “Consultar un Pago” (Get Payment) endpoint and reconcile its status against your own record before you fulfill an order or mark a charge complete. This matters even more without a sandbox: during production testing your webhook handler runs against real events, so a handler that trusts the payload blindly can mark a cent-sized smoke test as “paid” and skip the refund, leaving real money parked in your account. Re-fetching from Get Payment also closes the loop when a webhook is delayed, duplicated, or lost mid-flight — webhooks are best-effort, not guaranteed once-only delivery. That is the same discipline behind multi-rail reconciliation in Mexico, and why polling-plus-webhook beats either alone in webhooks vs polling vs API.

// Node — source of truth is Clip's Get Payment, never the client or the webhook body
async function getPaymentStatus(paymentId, authHeader) {
  const res = await fetch(`https://api.payclip.com/payments/${paymentId}`, {
    headers: { Authorization: authHeader }, // Basic <base64 key:secret> in production
  });
  const payment = await res.json();
  // Compare payment.status against your local record before marking anything paid
  return payment;
}

Your before-you-go-live checklist for Clip online payments

Correct env: test_ vs live key asserted in coderequired
Idempotency key on every chargerequired
Prod-test amount capped + flag-gatedrequired
Reconcile via Get Payment, not the clientrequired
Webhook verified, status re-fetched from APIrequired
Every test charge refunded + loggedrequired

Run down the list before your first real customer:

  • Know your world. Building Checkout Transparente or Refunds? Use the real test credentials and test cards. Anything else? There is no sandbox — use the production ritual.
  • Assert the credential. Test and live hit the same URL — test creds are Bearer test_..., live creds are Basic. A mislabeled credential charges real money, so guard on the test_ prefix in code.
  • Tokenize right before charging. The Card Token ID expires in 15 minutes.
  • Idempotency on every charge, so a retried QA run or a doubled click never double-charges.
  • Cap and flag-gate production test charges; refund and log each one.
  • Reconcile against Clip’s API. Get Payment is your source of truth, not the client response and not the raw webhook.

Sources worth bookmarking: the Clip developer home, the FAQ that says “no sandbox,” and the /reference/pruebas page that now ships test credentials — read both, because in 2026 they disagree, and which one applies depends entirely on the flow you’re shipping. When you’re ready to wire the checkout itself, continue with the sibling Integrate Clip payments (checkout modes, Node) and the broader LATAM payments hub.