Skip to main content

Overview

The payment webhook endpoint receives notifications from MercadoPago when payment events occur. It automatically activates user subscriptions, creates audit logs, and handles idempotent processing to prevent duplicate activations.
This endpoint is implemented as a Cloudflare Worker for high reliability and low latency processing of payment notifications.

Endpoint

POST https://mercadopago-jcv.fagal142010.workers.dev/webhook
POST https://mercadopago-jcv.fagal142010.workers.dev/api/webhooks/mercadopago

Authentication

The webhook is called directly by MercadoPago. No authentication headers are required from the client, but the worker:
  1. Validates the payment ID with MercadoPago API
  2. Uses MercadoPago’s API to fetch payment details
  3. Implements idempotency checks to prevent duplicate processing
Security: The webhook URL should be registered in your MercadoPago account settings. Do not expose the webhook endpoint publicly as it can create subscriptions.

Webhook Payload

MercadoPago sends notifications in the following format:
type
string
required
Notification type. Must be payment for payment events.
action
string
required
Event action. Typically payment.created or payment.updated.
data
object
required
Payment data object

Example Webhook Payload

{
  "action": "payment.updated",
  "api_version": "v1",
  "data": {
    "id": "1234567890"
  },
  "date_created": "2024-03-01T10:00:00Z",
  "id": 12345,
  "live_mode": true,
  "type": "payment",
  "user_id": "123456789"
}

Processing Flow

The webhook processes payments through the following steps:

1. Receive & Log

Every webhook notification is immediately logged to the webhook_logs table with status received.

2. Validate Event Type

Only payment type notifications with actions payment.created or payment.updated are processed. Other notifications are logged and ignored.

3. Idempotency Check

The webhook checks if the same payment_id + action combination was already successfully processed. If yes, it returns success without creating duplicate subscriptions.

4. Fetch Payment Details

The worker fetches full payment details from MercadoPago API:
GET https://api.mercadopago.com/v1/payments/{payment_id}
Authorization: Bearer {MP_ACCESS_TOKEN}

5. Validate Payment Status

Only payments with status approved proceed to subscription activation. Other statuses are logged for monitoring.

6. User Lookup

The webhook attempts to find the user through multiple methods:
1

Metadata User ID

Check if metadata.user_id exists in the payment
2

External Reference

Extract user ID from external_reference (format: JCV-timestamp-userId)
3

Payer Email

Look up user by payer.email in the profiles table

7. Plan Detection

The plan type is determined from the payment amount:
Amount (COP)Plan TypeDuration
49,900PLAN_BASICO40 days
89,900PLAN_PRO40 days
149,900PLAN_PREMIUM40 days
If the amount doesn’t match, it falls back to metadata.plan_type or defaults to PLAN_BASICO.

8. Subscription Creation

The webhook creates a new subscription in Supabase:
INSERT INTO subscriptions (
  user_id,
  plan_type,
  status,
  start_date,
  end_date,
  payment_provider,
  payment_reference,
  amount_paid
)

9. Profile Update

Updates the user’s profile with active subscription information:
UPDATE profiles SET
  has_active_subscription = true,
  current_plan = 'PLAN_PRO',
  subscription_end_date = '2024-04-10T10:00:00Z'
WHERE id = user_id

10. Audit Log

Creates an entry in subscription_audit_log for compliance and debugging:
{
  "subscription_id": "sub-uuid",
  "user_id": "user-uuid",
  "operation": "activated",
  "trigger_source": "webhook",
  "trigger_reference": "1234567890",
  "metadata": {
    "webhook_log_id": "log-uuid",
    "user_lookup_method": "payer_email",
    "payment_status": "approved"
  }
}

Response

Success Response

received
boolean
Always true when webhook is received
processed
boolean
true if subscription was activated, false if ignored
subscription
object
Subscription activation details
log_id
string
UUID of the webhook log entry for debugging

Example Success Response

{
  "received": true,
  "processed": true,
  "subscription": {
    "status": "activated",
    "subscription_id": "550e8400-e29b-41d4-a716-446655440000",
    "user_id": "7b9e6f12-3c45-4a21-8e9d-1234567890ab",
    "plan_type": "PLAN_PRO",
    "expires": "2024-04-10T10:00:00.000Z"
  },
  "log_id": "6a8d5c21-4b32-4f18-9c8e-0987654321ba"
}

Ignored Response

Returned when the webhook is received but not processed:
{
  "received": true,
  "ignored": true,
  "reason": "non-payment",
  "log_id": "webhook-log-uuid"
}
or
{
  "received": true,
  "duplicate": true,
  "original_log_id": "original-log-uuid",
  "log_id": "current-log-uuid"
}

Error Handling

The webhook implements intelligent error handling with retry logic:

Error Types

Should Retry: NoStatus Code: 500Cause: Missing environment variables (MP_ACCESS_TOKEN, SUPABASE_URL, SUPABASE_SERVICE_KEY)Action: Fix server configuration and redeploy
Should Retry: NoStatus Code: 200 (to prevent MercadoPago retries)Cause: Invalid or expired MercadoPago access tokenAction: Update MP_ACCESS_TOKEN in environment variables
Should Retry: NoStatus Code: 200 (to prevent MercadoPago retries)Cause: Payment ID doesn’t exist in MercadoPagoAction: Investigate if payment was deleted or notification is invalid
Should Retry: NoStatus Code: 200Cause: User not found, invalid payment dataAction: Check that user exists before payment or fix data mapping
Should Retry: YesStatus Code: 500Cause: DNS failure, timeout, connection refusedAction: MercadoPago will automatically retry
Should Retry: YesStatus Code: 500Cause: Too many requests to MercadoPago APIAction: Wait and retry, MercadoPago will handle retry logic
Should Retry: YesStatus Code: 500Cause: MercadoPago API or Supabase temporary errorAction: MercadoPago will automatically retry

Retry Strategy

MercadoPago automatically retries webhook deliveries for 48 hours when it receives a 5xx response or connection failure. The webhook returns 200 for permanent errors (AUTH, NOT_FOUND, VALIDATION) to prevent unnecessary retries.

Webhook Logs

All webhook notifications are logged to the webhook_logs table:
ColumnDescription
idUUID primary key
statusreceived, processing, success, failed, ignored
webhook_typeEvent type from MercadoPago
webhook_actionAction type (payment.created, payment.updated)
payment_idMercadoPago payment ID
payment_statusPayment status (approved, pending, rejected)
payment_amountPayment amount
user_emailPayer’s email
user_idJCV Fitness user UUID
subscription_idCreated subscription UUID
plan_typeActivated plan type
is_duplicateBoolean flag for duplicate webhooks
error_messageError message if failed
processing_time_msProcessing duration in milliseconds
supabase_operationsJSON array of database operations
raw_payloadFull webhook payload
received_atWebhook received timestamp
processed_atProcessing completed timestamp

Health Check

The webhook endpoint supports health checks:
GET https://mercadopago-jcv.fagal142010.workers.dev/webhook
Response:
{
  "status": "ok",
  "service": "mercadopago-webhook"
}

Webhook Flow Diagram

Configuring the Webhook in MercadoPago

1

Access MercadoPago Dashboard

2

Navigate to Webhooks

In your application settings, find the “Webhooks” or “Notifications” section
3

Add Production URL

Enter the webhook URL:
https://mercadopago-jcv.fagal142010.workers.dev/webhook
4

Select Events

Subscribe to these events:
  • payment.created
  • payment.updated
5

Test Webhook

Use MercadoPago’s webhook testing tool to send a test notification

Testing

Manual Testing

You can manually trigger the webhook using curl:
curl -X POST https://mercadopago-jcv.fagal142010.workers.dev/webhook \
  -H "Content-Type: application/json" \
  -d '{
    "type": "payment",
    "action": "payment.updated",
    "data": {
      "id": "1234567890"
    }
  }'

Monitoring Webhooks

Query the webhook logs to monitor webhook processing:
SELECT 
  id,
  status,
  payment_id,
  payment_status,
  user_email,
  plan_type,
  processing_time_ms,
  error_message,
  received_at,
  processed_at
FROM webhook_logs
ORDER BY received_at DESC
LIMIT 50;

Common Issues

Cause: User doesn’t exist in database when payment is madeSolution: Ensure users are created before initiating payment, or implement auto-registration based on payer email
Cause: Multiple webhooks for the same paymentSolution: The webhook has built-in idempotency - check is_duplicate flag in logs
Cause: Payment amount doesn’t match PLAN_CONFIGSolution: Update PLAN_CONFIG in worker or ensure correct amount is sent

Security Considerations

Signature Verification: Currently, the webhook validates payments by fetching from MercadoPago API. For additional security, consider implementing signature verification using x-signature header.
IP Whitelisting: Consider adding Cloudflare Workers firewall rules to only accept webhooks from MercadoPago’s IP ranges.

Create Payment Preference

Create MercadoPago payment preferences

Additional Resources

Build docs developers (and LLMs) love