---
title: "Mercado Pago Webhooks in Hospitality: IPN Processing, Idempotency & VikBooking Synchronization"
description: "Architect a resilient system for processing Mercado Pago webhooks in hotel booking engines. HMAC-SHA256 verification, idempotency locks, and VikBooking synchronization."
date: 2026-08-29
category: "Dev Finance"
imageUrl: "/assets/images/blog/webhooks-mercado-pago-hoteleria.webp"
imageAlt: "Architecture diagram of Mercado Pago webhooks and IPN with cryptographic HMAC verification and synchronization queues for hotel room bookings."
readTime: "11 min read"
author: "DoneAPI Engineering Team"
tags: ["Mercado Pago", "Webhooks", "VikBooking", "WordPress", "Idempotency", "Fintech", "Hospitality"]
lang: "en"
translationSlug: "webhooks-mercado-pago-hoteleria-notificaciones-ipn"
featured: false
---

Payment processing within the hospitality and travel industry presents operational hazards virtually absent in traditional physical e-commerce. When a guest reserves a boutique hotel suite for a holiday weekend, that inventory is finite and perishable: a room held in limbo that fails to clear payment represents lost revenue, while a room confirmed twice due to a concurrency race condition causes disastrous overbooking that destroys guest trust.

Many WordPress-based booking engines, such as **VikBooking**, rely on third-party payment gateways. In Latin America, **Mercado Pago** is the undisputed market leader thanks to its seamless integration with regional payment rails (credit cards, debit cards, PSE in Colombia, Pix in Brazil, SPEI & OXXO in Mexico). However, the checkout transaction does not conclude when a traveler submits their card details. The true resilience of a hospitality platform depends entirely on how it processes **asynchronous payment notifications (Webhooks and IPN)**.

In this engineering guide, we dissect the architecture required to ingest, cryptographically verify, and process Mercado Pago webhooks while enforcing **strict idempotency**, preventing inventory race conditions, and synchronizing VikBooking reservations in sub-second intervals.

---

## 1. The Perishable Inventory Problem in Hospitality Checkout Flows

In standard retail e-commerce, a 30-second webhook delay while a customer waits on a confirmation page is largely benign. In hospitality, reservation lifecycles follow a strict state machine:

```text
[Guest Initiates Checkout]
          │
          ▼
[State: PENDING / Temporary Room Hold (15 min)]
          │
          ├───► (Webhook: payment.created / in_process) ──► Retain temporary hold
          │
          ├───► (Webhook: payment.approved) ──────────────► [State: CONFIRMED] + Issue Voucher
          │
          ├───► (Webhook: payment.rejected / cancelled) ──► [State: CANCELLED] + Release Room
          │
          └───► (15-min timeout with zero webhook) ───────► Release Room Dates in VikBooking
```

If the hotel backend fails to handle asynchronous Mercado Pago notifications defensively, three critical failures emerge:

1. **Timeout False Positives:** A guest pays via bank transfer (PSE or Pix), but server latency delays webhook delivery. VikBooking's background cron cancels the expired reservation and re-opens room availability. When the webhook finally arrives, it confirms a room that has already been resold to another guest.
2. **Duplication via HTTP Retry Storms:** Mercado Pago enforces an aggressive exponential backoff retry policy. If your webhook listener takes longer than 5,000 milliseconds to respond with HTTP `200` or `201`, Mercado Pago resends the identical event. Non-idempotent code risks dispatching multiple confirmation emails and corrupting PMS ledgers.
3. **Payment Spoofing Injections:** If your endpoint fails to cryptographically verify the HMAC signature dispatched in the request headers, an attacker can submit fabricated `POST` payloads mimicking approved payments, unlocking reservations without paying a single dollar.

---

## 2. Legacy IPN vs. Mercado Pago Webhooks v2

Mercado Pago provides two distinct notification mechanisms. Understanding the technical divergence is crucial for avoiding obsolete legacy patterns:

| Technical Feature | Legacy IPN (Instant Payment Notification) | Modern Webhooks v2 (Real-Time Events) |
| :--- | :--- | :--- |
| **Delivery Mechanism** | Query parameters in the URL (`?id=123&topic=payment`) | Structured JSON payload in the HTTP `POST` body |
| **Signature Cryptography** | No native header signature; demanded manual reverse lookups | Mandatory `x-signature` header with timestamp & HMAC-SHA256 |
| **Event Granularity** | Limited to basic payments and subscription plans | Rich event taxonomy: `payment`, `chargebacks`, `merchant_order` |
| **Retry Mechanics** | Rigid linear retries | Resilient exponential backoff with latency tracking |
| **DoneAPI Recommendation** | Maintained for legacy compatibility; strictly discouraged | **Mandatory production standard for enterprise hospitality** |

> 💡 **Golden Rule in Fintech Architecture:** Never deduce the final financial state solely from the webhook body. The webhook should be interpreted strictly as an **advisory signal**: *"State changed for resource X"*. Your server must verify the cryptographic signature and then execute an authenticated `GET /v1/payments/{id}` call directly to Mercado Pago’s REST API to retrieve the verified ledger state.

---

## 3. Cryptographic Security: Verifying HMAC-SHA256 Signatures

Mercado Pago transmits an HTTP header named `x-signature` alongside each Webhook v2 dispatch. This header contains two key components separated by commas:
- `ts`: A UNIX timestamp indicating the exact second Mercado Pago dispatched the payload.
- `v1`: The HMAC-SHA256 hash computed with your private webhook secret key.

### Verification Algorithm:
1. Parse `ts` and `v1` from the `x-signature` header.
2. Verify that `ts` is within 300 seconds (5 minutes) of the current server timestamp. This completely eliminates **Replay Attacks**.
3. Reconstruct the raw manifest template:
   ```text
   id:[event_data_id];request-id:[x-request-id];ts:[timestamp];
   ```
4. Compute the HMAC-SHA256 hash and execute a timing-attack safe comparison using `hash_equals()`.

---

## 4. Production PHP Listener Implementation for VikBooking

The following implementation illustrates how to handle inbound webhooks with cryptographic validation, API state verification, and safe VikBooking reservation mutation:

```php
<?php
declare(strict_types=1);

namespace DoneApi\Plugin\Gateways;

use WP_REST_Request;
use WP_REST_Response;

class MercadoPagoWebhookHandler {
    private string $secret_key;
    private string $access_token;

    public function __construct(string $secret_key, string $access_token) {
        $this->secret_key   = $secret_key;
        $this->access_token = $access_token;
    }

    public function process_inbound_webhook(WP_REST_Request $request): WP_REST_Response {
        $signature_header = $request->get_header('x-signature') ?? '';
        $request_id       = $request->get_header('x-request-id') ?? '';
        $body             = $request->get_json_params() ?? [];

        // 1. Verify HMAC Cryptographic Signature
        if (!$this->verify_signature($signature_header, $request_id, $body)) {
            return new WP_REST_Response(['error' => 'Invalid HMAC Signature'], 401);
        }

        $event_type = $body['type'] ?? '';
        $payment_id = $body['data']['id'] ?? null;

        if ($event_type !== 'payment' || !$payment_id) {
            return new WP_REST_Response(['status' => 'ignored'], 200);
        }

        // 2. Query Verified Transaction State from Mercado Pago REST API
        $payment_data = $this->fetch_verified_payment((string) $payment_id);
        if (!$payment_data) {
            return new WP_REST_Response(['error' => 'Payment not found in gateway'], 404);
        }

        $booking_id = $payment_data['external_reference'] ?? null;
        $status     = $payment_data['status'] ?? '';

        if (!$booking_id) {
            return new WP_REST_Response(['status' => 'missing_external_reference'], 200);
        }

        // 3. Acquire Distributed Lock & Apply Idempotent Mutation
        $this->synchronize_vikbooking((int) $booking_id, (string) $payment_id, $status, $payment_data);

        return new WP_REST_Response(['status' => 'acknowledged'], 200);
    }

    private function verify_signature(string $signature_header, string $request_id, array $body): bool {
        if (empty($signature_header)) return false;

        $parts = [];
        foreach (explode(',', $signature_header) as $part) {
            [$key, $val] = explode('=', trim($part), 2);
            $parts[$key] = $val;
        }

        $ts = $parts['ts'] ?? null;
        $v1 = $parts['v1'] ?? null;

        if (!$ts || !$v1 || (abs(time() - (int) $ts) > 300)) {
            return false; // Replay attack guard
        }

        $data_id = $body['data']['id'] ?? '';
        $manifest = "id:{$data_id};request-id:{$request_id};ts:{$ts};";
        $expected = hash_hmac('sha256', $manifest, $this->secret_key);

        return hash_equals($expected, $v1);
    }

    private function fetch_verified_payment(string $payment_id): ?array {
        $response = wp_remote_get("https://api.mercadopago.com/v1/payments/{$payment_id}", [
            'headers' => [
                'Authorization' => "Bearer {$this->access_token}",
                'Accept'        => 'application/json',
            ],
            'timeout' => 10,
        ]);

        if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
            return null;
        }

        return json_decode(wp_remote_retrieve_body($response), true);
    }

    private function synchronize_vikbooking(int $booking_id, string $payment_id, string $status, array $data): void {
        global $wpdb;
        $table_events = $wpdb->prefix . 'doneapi_mp_processed_events';

        // Enforce strict relational idempotency
        $inserted = $wpdb->query($wpdb->prepare(
            "INSERT IGNORE INTO {$table_events} (payment_id, booking_id, status, processed_at) VALUES (%s, %d, %s, NOW())",
            $payment_id,
            $booking_id,
            $status
        ));

        // If duplicate execution detected, skip PMS status mutations
        if ($inserted === 0) {
            return;
        }

        if ($status === 'approved') {
            // Confirm room booking in VikBooking and dispatch guest confirmation voucher
            if (class_exists('\VikBooking')) {
                \VikBooking::setBookingStatus($booking_id, 'confirmed');
            }
        } elseif (in_array($status, ['rejected', 'cancelled'])) {
            // Unlock room inventory in VikBooking
            if (class_exists('\VikBooking')) {
                \VikBooking::setBookingStatus($booking_id, 'cancelled');
            }
        }
    }
}
```

---

## 5. Three-Tier Idempotency Pattern

Idempotency guarantees that executing an operation multiple times yields the exact same state as executing it once. In a distributed hospitality environment, idempotency is enforced across three sequential tiers:

```text
[Mercado Pago Webhook Ingress]
               │
               ▼ (Tier 1: Network & Clock Skew Guard)
   Validate ts timestamp & reject replay attempts (> 300s)
               │
               ▼ (Tier 2: Distributed Lock / In-Memory Mutex)
   Acquire Redis Mutex: "lock:payment:{payment_id}"
               │
               ▼ (Tier 3: Relational ACID Persistence)
   INSERT IGNORE INTO wp_doneapi_mp_processed_events (payment_id, booking_id)
   Skip execution if record already exists
```

### Dedicated Event Auditing Table

To protect WordPress from state drift, we maintain a dedicated transaction table inside MySQL (InnoDB):

```sql
CREATE TABLE `wp_doneapi_mp_processed_events` (
  `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `payment_id` VARCHAR(64) NOT NULL,
  `booking_id` INT(11) UNSIGNED NOT NULL,
  `status` ENUM('PENDING','PROCESSING','COMPLETED','FAILED') NOT NULL DEFAULT 'PENDING',
  `processed_at` DATETIME NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_payment_id` (`payment_id`),
  KEY `idx_booking_id` (`booking_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

---

## 6. Turnkey Production Solution: DoneAPI VikBooking Mercado Pago Plugin ($7 USD)

If you manage a hotel, resort, boutique glamping retreat, or property rental agency and need to connect **VikBooking with Mercado Pago** without months of bespoke development, **DoneAPI** provides the definitive production-ready plugin:

- **Native VikBooking Integration:** Full compatibility with the latest versions of VikBooking for WordPress.
- **Automated Cryptographic Verification:** Native validation of `x-signature` headers and Webhooks v2.
- **Comprehensive LATAM Currency Rails:** Multi-currency support for Colombian Pesos (COP), Mexican Pesos (MXN), Brazilian Reais (BRL), and US Dollars (USD).
- **Zero Recurring Subscription Fees:** Permanent software license for a one-time payment of **$7 USD**.

> 💬 **Looking to install the official plugin on your site or require custom enterprise architecture for your hotel chain?** Reach out directly via WhatsApp for setup support and turnkey onboarding.

<div class="my-8 p-6 bg-slate-900 border border-emerald-500/30 rounded-2xl shadow-xl flex flex-col md:flex-row items-center justify-between gap-6">
  <div>
    <h3 class="text-xl font-bold text-white mb-2">Get the VikBooking Mercado Pago Plugin ($7 USD) or Request Advisory</h3>
    <p class="text-slate-300 text-sm max-w-xl">Accelerate your hotel’s direct booking revenue with instant webhook reconciliation, zero overbooking, and seamless checkout.</p>
  </div>
  <a href="https://wa.me/573208173939?text=Hello%20DoneAPI,%20I%20want%20to%20purchase%20the%20VikBooking%20Mercado%20Pago%20plugin%20for%20$7%20USD%20or%20request%20technical%20advisory." target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 px-6 py-3.5 bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-bold rounded-xl transition-all shadow-lg hover:shadow-emerald-500/25 shrink-0 text-sm">
    <svg class="w-5 h-5 fill-current" viewBox="0 0 24 24"><path d="M.057 24l1.687-6.163c-1.041-1.804-1.588-3.849-1.587-5.946.003-6.556 5.338-11.891 11.893-11.891 3.181.001 6.167 1.24 8.413 3.488 2.245 2.248 3.481 5.236 3.48 8.414-.003 6.557-5.338 11.892-11.893 11.892-1.99-.001-3.951-.5-5.688-1.448l-6.305 1.654zm6.597-3.807c1.676.995 3.276 1.591 5.392 1.592 5.448 0 9.886-4.434 9.889-9.885.002-5.462-4.415-9.89-9.881-9.892-5.452 0-9.887 4.434-9.889 9.884-.001 2.225.651 3.891 1.746 5.634l-.999 3.648 3.742-.981zm11.387-5.464c-.074-.124-.272-.198-.57-.347-.297-.149-1.758-.868-2.031-.967-.272-.099-.47-.149-.669.149-.198.297-.768.967-.941 1.165-.173.198-.347.223-.644.074-.297-.149-1.255-.462-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.297-.347.446-.521.151-.172.2-.296.3-.495.099-.198.05-.372-.025-.521-.075-.148-.669-1.611-.916-2.206-.242-.579-.487-.501-.669-.51l-.57-.01c-.198 0-.52.074-.792.372s-1.04 1.016-1.04 2.479 1.065 2.876 1.213 3.074c.149.198 2.095 3.2 5.076 4.487.709.306 1.263.489 1.694.626.712.226 1.36.194 1.872.118.571-.085 1.758-.719 2.006-1.413.248-.695.248-1.29.173-1.414z"/></svg>
    Speak with an Engineer on WhatsApp
  </a>
</div>

---

## 7. Conclusion

Designing an enterprise payment integration for the hospitality sector tolerates zero shortcuts. Combining HMAC-SHA256 signature verification, active reverse lookups against Mercado Pago's `/v1/payments/{id}` endpoint, and strict database idempotency constraints allows your booking engine to process hundreds of concurrent reservations with mathematical certainty.

By embedding these architectural standards into **VikBooking**, you completely eliminate overbooking risks from network retries and provide travelers with a fast, reliable, and secure direct checkout experience.
