How to Integrate Mercado Pago Into Your App (2026): Checkout Pro, Bricks, and Webhooks That Don't Lie — Cesar Ayala
← All posts

How to Integrate Mercado Pago Into Your App (2026): Checkout Pro, Bricks, and Webhooks That Don't Lie

Integrate Mercado Pago by creating a preference server-side, redirecting the buyer to Checkout Pro's hosted page, then treating the signed webhook—not the browser redirect—as the source of truth. Validate the x-signature header, fetch the payment by id, confirm status is approved, and only then grant access.

How do you actually integrate Mercado Pago (and where do you grant access)?

Integrate Mercado Pago by creating a preference server-side, redirecting the buyer to Checkout Pro’s hosted page, and then treating the signed webhook — not the browser redirect — as the source of truth. Validate the x-signature header, fetch the payment by id, confirm status === "approved", and only then grant access.

Here is the one rule I want burned into your handler before you write a line of UI: the redirect is for the user, the verified webhook is for your database. When Checkout Pro finishes, Mercado Pago bounces the browser back to one of your back_urls. That bounce is UX. It is not proof that money moved. The buyer can close the tab before it fires, anyone with the link can replay it, and — this is the part that bites in Mexico specifically — half the popular payment methods here don’t settle at checkout at all. An OXXO voucher can sit pending for two days. If you flip a “paid” flag on the redirect, you ship two revenue bugs: false grants (you provisioned, the cash voucher was never paid) and double grants (the buyer refreshes the success page and gets provisioned twice).

Payments are asynchronous. The API call and the redirect are only a request. The truth lands later, as a signed webhook notification. I learned this discipline wiring Stripe into FinHOA and Mercanto, and it transfers one-for-one to Mercado Pago — same spine, different gateway. On Mercanto, our B2B marketplace, and again at Nixbly, the bugs that cost real money all traced back to trusting the redirect instead of the event. Mercado Pago is the dominant gateway across Mexico and LATAM — it carries consumer trust and the local methods (OXXO, SPEI, MSI, the MP wallet) built in — which is exactly why getting its webhook spine right matters: you’re processing a lot of volume through methods that settle on their own clock.

You have three ways to integrate, from most-managed to most-control: Checkout Pro (hosted page, you redirect), Checkout Bricks (embeddable front-end components), and the Checkout API (Checkout Transparente — you build everything). Whichever you pick, the create-preference, redirect, webhook, verify, grant flow is the same. Get that spine right and most of the double-grant and false-grant bugs never happen.

The webhook-as-source-of-truth flow for Mercado Pago

Create preferenceYour server builds items + back_urls + notification_url, gets an init_point
Redirect to Checkout ProMP's hosted page collects payment; back_urls are UX only
MP fires a webhookNotification carries the payment id + topic/type
Validate x-signatureHMAC with your webhook secret confirms the event is genuinely from MP
Fetch the payment by idCall the Payments API — never trust the notification body alone
Confirm approvedOnly status === 'approved' counts as money in
Grant access (once)Idempotent grant keyed to the payment id
Access is granted on the signature-verified, approved webhook — never on the browser redirect.

Checkout Pro, Checkout Bricks, or the Checkout API: which one should you pick?

The three options trade control against effort and PCI scope. More control over the UI means more code and a bigger compliance footprint. Here’s how I size it up.

Checkout Pro is the hosted path. You create a preference server-side, get back an init_point URL, and redirect the buyer to Mercado Pago’s own checkout page. MP renders all the payment methods, handles the card fields, and carries the PCI burden. You write almost no front-end. This is the fastest to ship and the lightest PCI scope, and it’s where I start every project unless there’s a concrete reason not to. If you’re a B2C store or a SaaS that just needs to get paid, this is the answer.

Checkout Bricks is the middle ground. Bricks are modular, embeddable front-end components — Payment Brick, Card Payment Brick, Wallet Brick, Status Screen Brick — that you drop into your own page. You get a branded, in-context checkout that looks like your product instead of a redirect, but the sensitive fields are still MP-managed inside the components. Reach for Bricks when you have a real UX reason to keep the buyer on your page (a branded checkout, an embedded flow) and you’re willing to write more front-end to get it.

The Checkout API (Checkout Transparente / Core) is full control. You build the entire UI and create payments server-side via the API. You own the look, the flow, and the largest PCI scope. This is for fully custom in-app checkout where nothing else fits — and you should be honest about the compliance cost before you sign up for it.

My opinionated default: start with Checkout Pro. Move to Bricks only when UX demands the buyer stay on your page, and reach for the raw Checkout API only when both of those fail you. Most teams that “need” the Checkout API actually wanted Bricks.

Checkout Pro vs Bricks vs the Checkout API

Checkout Pro (hosted)

  • Create a preference, redirect to init_point
  • MP renders methods + owns card fields
  • Lightest PCI scope
  • Fastest to ship
  • Best for B2C stores + most SaaS

Bricks / Checkout API

  • Bricks: embeddable components on your page
  • Checkout API: you build the whole UI server-side
  • More UX control, more front-end code
  • Larger PCI scope (largest for Checkout API)
  • Bricks for branded checkout; API for full custom in-app
Control trades directly against effort and PCI scope. For most apps, Checkout Pro is the right default.

What does the create-preference to redirect flow look like (with code)?

Start in the Developer Dashboard. Create an application under Your Integrations, grab your credentials, and set up test users and the sandbox there before you touch production. You’ll use the official server SDK (Node mercadopago) for creating preferences and verifying webhooks, and the JS SDK (MercadoPago.js / sdk-js) on the front end if you go the Bricks route.

A preference is the server-side object that describes the purchase: the items, the back_urls (where MP returns the buyer for success, failure, and pending outcomes), and — the load-bearing field — the notification_url where MP will POST the webhook later. Create it with the Preference resource, then redirect the buyer to the init_point it returns.

import { MercadoPagoConfig, Preference } from "mercadopago";

const client = new MercadoPagoConfig({ accessToken: process.env.MP_ACCESS_TOKEN });

export async function createCheckout(order) {
  const preference = await new Preference(client).create({
    body: {
      items: [
        {
          title: order.title,
          quantity: 1,
          unit_price: order.amount,   // MXN
          currency_id: "MXN",
        },
      ],
      back_urls: {
        success: `${process.env.APP}/checkout/success`,
        failure: `${process.env.APP}/checkout/failure`,
        pending: `${process.env.APP}/checkout/pending`,   // OXXO/SPEI land here
      },
      notification_url: `${process.env.APP}/webhooks/mercadopago`,  // wires the webhook
      external_reference: order.id,    // map MP -> your order
    },
  });

  return preference.init_point;        // redirect the buyer here; grant NOTHING yet
}

Two things to internalize. First, the pending back URL is not optional in Mexico — OXXO and SPEI buyers land there because they haven’t paid yet, and your page should say “we’re waiting for your payment,” not “thanks, you’re in.” Second, the redirect to init_point grants nothing. Setting notification_url here is what wires the webhook to fire later — that event, not this redirect, is where access happens. Keep the external_reference on the preference so you can tie the eventual payment back to your own order row.

Integrate Checkout Pro end to end

  1. Get credentialsCreate an app under 'Your Integrations' in the Developer Dashboard
  2. Install the Node SDKnpm i mercadopago; configure with your access token
  3. Create the preferenceitems + back_urls (success/failure/pending) + notification_url
  4. Redirect to init_pointSend the buyer to MP's hosted Checkout Pro page
  5. Receive the webhookMP POSTs to notification_url with the payment id + topic
  6. Verify + fetchValidate x-signature, then fetch the payment by id from the API
  7. Grant on approvedOnly status === 'approved' provisions — exactly once
Seven steps from credentials to a verified grant. The grant happens at step 7, never at step 4.

Why is the verified webhook the only source of truth (validate x-signature, then fetch)?

This is the security spine, and it’s the same discipline as the Stripe post: never grant access on data you didn’t verify. Mercado Pago’s newer Webhooks supersede the legacy IPN — use Webhooks. The notification that arrives carries a payment id and a topic/type, and almost nothing else you should trust. Treat the body as a hint that something happened, not as fact.

Two steps make it real. First, validate the x-signature header — it’s an HMAC built with your webhook secret, and it’s what proves the notification genuinely came from Mercado Pago rather than someone hitting your public endpoint. Second, fetch the payment by id from the Payments API and read its real status. Only status === "approved" is money in. Anything else — pending, in_process, rejected — gets no grant.

One detail trips up almost everyone: Mercado Pago’s signature manifest expects the data.id lowercased (its documented template is id:[data.id_lowercase];request-id:[x-request-id];ts:[ts];). Numeric ids don’t care, but some topics carry alphanumeric ids — lowercase it when you build the manifest, and keep the original-case id for the actual fetch.

import crypto from "node:crypto";
import { MercadoPagoConfig, Payment } from "mercadopago";

const client = new MercadoPagoConfig({ accessToken: process.env.MP_ACCESS_TOKEN });

export async function handleWebhook(req, res) {
  // data.id can arrive in the query string or the body, depending on the topic.
  const paymentId = req.query["data.id"] || req.body?.data?.id;

  // 1) Validate the x-signature HMAC before trusting anything.
  const signature = req.headers["x-signature"] || "";
  const requestId = req.headers["x-request-id"] || "";
  const parts = Object.fromEntries(
    signature.split(",").map((p) => {
      const i = p.indexOf("=");
      return [p.slice(0, i).trim(), p.slice(i + 1)];
    })
  );

  // The manifest id MUST match the data.id MP sent, lowercased per MP's template.
  const dataId = String(paymentId).toLowerCase();
  const manifest = `id:${dataId};request-id:${requestId};ts:${parts.ts};`;
  const expected = crypto
    .createHmac("sha256", process.env.MP_WEBHOOK_SECRET)
    .update(manifest)
    .digest("hex");

  // Constant-time compare — guard for equal length first.
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 || "");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.sendStatus(401);   // forged or replayed
  }

  // 2) Fetch the payment by id — never trust the notification body.
  const payment = await new Payment(client).get({ id: paymentId });

  if (payment.status === "approved") {
    await grantAccessOnce(payment.external_reference, payment.id);  // idempotent
  }

  return res.sendStatus(200);   // 2xx fast, or MP keeps retrying
}

Two more disciplines carry straight over from running Stripe in production. Idempotency: notifications retry, and the same approved payment can arrive more than once — key your grant to the payment id (grantAccessOnce) so a refresh or a retry never provisions twice or fires two confirmation emails. Return 2xx fast: acknowledge the webhook in milliseconds and offload anything heavy, or Mercado Pago will keep retrying a request it thinks failed. This whole pattern — verify, fetch, then act — is the webhook-over-polling integration model underneath; the event tells you when, the API call tells you what’s true.

Which Mexican payment methods do you get (cards, MSI, OXXO, SPEI, wallet)?

This is where Mercado Pago earns its place in Mexico, and where the async wrinkle makes webhook-as-truth non-negotiable. Checkout Pro surfaces these methods automatically — you don’t wire them one by one.

Credit and debit cards, plus MSI (meses sin intereses — interest-free installments). MSI is the single biggest conversion lever on higher-ticket carts in Mexico; buyers expect to split a large purchase across 3, 6, or 12 months, and offering it visibly moves the needle on B2C checkout.

OXXO is the cash voucher. The buyer gets a reference, walks into an OXXO store, and pays in cash — later. The payment sits pending until the cash settles, which can be hours or days. This is the textbook case for waiting on the approved webhook: if you provision when the voucher is generated, you’ve given away product for cash that may never arrive.

SPEI is the interbank bank transfer. It’s common for larger B2B invoices, and like OXXO it settles asynchronously — the buyer initiates the transfer on their bank’s clock, not yours.

The Mercado Pago wallet is stored balance in the buyer’s MP account. It carries high consumer trust in Mexico and tends to convert well precisely because the buyer is already logged in and funded.

The through-line: OXXO and SPEI can sit pending while the buyer is long gone from your success page. That is exactly why the browser redirect can’t be your grant signal — only the settled, approved webhook can. If you’re building for the Mexican market, method coverage isn’t a nice-to-have, it’s the difference between a checkout locals trust and one they abandon.

Mexican payment methods in Mercado Pago

CardsCredit/debit — settle at checkout
MSIMeses sin intereses — biggest conversion lever on big carts
OXXOCash voucher — PENDING until paid in store (async)
SPEIBank transfer — common for B2B, settles later (async)
MP walletStored MP balance — high consumer trust, converts well
Async methods (OXXO, SPEI) settle after checkout — wait for the approved webhook before you grant.

What does Mercado Pago cost, and what are the gotchas?

On fees, I’ll be honest about what I can and can’t promise: Mercado Pago’s rates vary by country, payment method, and payout speed, and they move. As of 2026 the rule of thumb is that faster settlement costs more — if you want your money sooner, you pay a higher rate; if you can wait, you pay less. I won’t quote you an exact percentage because it would be stale by the time you read it. Check the Mercado Pago developer site for Mexico and confirm current pricing before you set your own prices, and model the method mix — MSI, OXXO, and cards don’t all cost the same.

The practitioner gotchas are where I see real launches break:

Don’t conflate pending with approved. This is the number-one mistake on Mexican integrations. OXXO and SPEI live in pending by design. Your grant gate is approved, full stop — read the actual status off the fetched payment, never infer it from the redirect or the notification topic.

Test vs production credentials are separate. Mercado Pago gives you distinct credentials and a distinct webhook secret per environment. Mix them — a test access token against a live webhook secret, say — and signature validation fails silently or payments don’t resolve, and you’ll burn an afternoon staring at a handler that looks correct. Test with the sandbox and test users under Your Integrations until the full preference-to-webhook loop works end to end, then swap to production credentials in one deliberate move.

Verify before you scale. Run a real OXXO test through the sandbox and confirm your handler waits for the approved webhook instead of granting on the pending one. If your test flow only ever uses an instantly-approved test card, you’ve never actually exercised the async path that real Mexican buyers will hit on day one.

For choosing between providers before you write any of this — Stripe vs Mercado Pago vs Conekta — I worked through the trade-offs in the payment gateways in Mexico comparison.

Mercado Pago integration FAQ

Checkout Pro vs Bricks vs the Checkout API — which do I use? Checkout Pro if you want the fastest, lightest-PCI path and a hosted page. Bricks if you need a branded checkout that keeps the buyer on your page. The Checkout API only if you need full custom in-app control and accept the largest PCI scope. Default to Checkout Pro.

Can I trust the back_urls / redirect to grant access? No. The redirect is UX only. Grant access exclusively on a signature-verified webhook where the fetched payment’s status is approved. The buyer can close the tab, replay the link, or be paying via a method that hasn’t settled yet.

How do I support OXXO and SPEI? They come with Checkout Pro automatically — no extra wiring. The catch is that both settle asynchronously, so you must wait for the approved webhook. The buyer leaves your site to pay; provision only when MP confirms the money arrived.

Do I need to validate the x-signature header? Yes. It’s the HMAC that proves the notification genuinely came from Mercado Pago. Your endpoint is public, so without signature validation anyone can POST you a fake “approved” notification. Validate first, then fetch the payment by id.

How do I test before going live? Use the sandbox and test users under Your Integrations in the Developer Dashboard. Run the full create-preference, redirect, webhook, verify, grant loop, and specifically test an async method (OXXO) so you exercise the pending-then-approved path, not just an instant card success.

Mercado Pago vs Stripe in Mexico? Mercado Pago wins on B2C consumer trust and built-in local methods (OXXO, SPEI, MSI, wallet); Stripe is strong for global card-first SaaS and subscriptions. I break down the full decision in the gateway comparison and the Stripe integration guide.

Closing: ship the boring, verified version

Here is the whole post in one breath: create the preference server-side, redirect the buyer to Checkout Pro, but grant access only on a signature-verified webhook whose fetched payment status is approved. Validate the x-signature, fetch by id, confirm approved, grant once. The redirect is for the user; the verified event is for your database.

Pick Checkout Pro by default and reach for Bricks or the Checkout API only when UX genuinely demands it. Lean into Mexico’s methods — MSI, OXXO, SPEI, and the MP wallet are what actually move conversion here — and respect that OXXO and SPEI settle on their own clock, which is the entire reason the webhook, not the redirect, is your source of truth.

None of this is clever. It’s boring and correct, and boring-and-correct is what keeps revenue from leaking while OXXO vouchers settle overnight. Read the official Mercado Pago Developers docs for the current API surface, and the Stripe integration guide if you want the same spine on a card-first SaaS.