Overview
The WebhookService class handles incoming webhook notifications from MercadoPago, validates their authenticity using HMAC signature verification, and extracts relevant payment information.
Source : src/Services/WebhookService.php:13
Webhooks are critical for reliable payment processing. They notify your application of payment status changes even if users don’t return to your site.
Constructor
public function __construct (
private CredentialResolverInterface $credentialResolver ,
) {}
Dependencies
credentialResolver
CredentialResolverInterface
required
Resolver for retrieving MercadoPago credentials including webhook secret. Automatically injected by Laravel’s service container.
Methods
handle()
Processes and validates an incoming webhook request from MercadoPago.
public function handle ( Request $request ) : array
Source : src/Services/WebhookService.php:19
Parameters
request
Illuminate\Http\Request
required
The incoming HTTP request from MercadoPago containing:
Webhook payload in request body
x-signature header for signature validation
x-request-id header for request identification
Query parameters including topic and data.id
Returns
Processed webhook data: Always true - indicates webhook was received
true if signature was validated, false if validation was skipped (no secret configured)
Event type: payment, merchant_order, subscription, etc. Extracted from topic query parameter or type field in payload.
Resource identifier (e.g., payment ID, order ID) Extracted from data.id field in payload or query parameter.
Complete webhook payload from MercadoPago
Throws
InvalidWebhookSignatureException
Thrown when:
Signature header is malformed (src/Services/WebhookService.php:75-76)
Computed signature doesn’t match provided signature (src/Services/WebhookService.php:57-59)
Usage
Controller injection
Route definition
Job dispatch
use Fitodac\LaravelMercadoPago\Services\ WebhookService ;
use Fitodac\LaravelMercadoPago\Exceptions\ InvalidWebhookSignatureException ;
use Illuminate\Http\ JsonResponse ;
use Illuminate\Http\ Request ;
final class WebhookController
{
public function store (
Request $request ,
WebhookService $webhookService
) : JsonResponse {
try {
$data = $webhookService -> handle ( $request );
// Process based on topic
match ( $data [ 'topic' ]) {
'payment' => $this -> handlePayment ( $data [ 'resource' ]),
'merchant_order' => $this -> handleOrder ( $data [ 'resource' ]),
default => \ Log :: info ( 'Unhandled webhook topic' , $data ),
};
return response () -> json ([ 'status' => 'processed' ]);
} catch ( InvalidWebhookSignatureException $e ) {
\ Log :: warning ( 'Invalid webhook signature' , [
'error' => $e -> getMessage (),
'ip' => $request -> ip (),
]);
return response () -> json ([ 'error' => 'Invalid signature' ], 401 );
}
}
private function handlePayment ( string $paymentId ) : void
{
// Retrieve payment details
$payment = app ( \Fitodac\LaravelMercadoPago\Services\ PaymentService :: class )
-> get ( $paymentId );
// Update order status
$status = data_get ( $payment , 'status' );
// ... update logic
}
}
Signature Validation
The service automatically validates webhook authenticity when a webhook secret is configured.
How validation works
Source : src/Services/WebhookService.php:38-60
Extract signature components from x-signature header:
ts: Timestamp
v1: HMAC-SHA256 hash
Build manifest string:
id:{resource_id};request-id:{request_id};ts:{timestamp};
Compute HMAC-SHA256 hash using webhook secret
Compare computed hash with provided signature
Throw InvalidWebhookSignatureException if mismatch
Configuration
Signature validation requires webhook secret configuration:
// config/mercadopago.php
return [
'webhook_secret' => env ( 'MERCADOPAGO_WEBHOOK_SECRET' ),
// ...
];
# .env
MERCADOPAGO_WEBHOOK_SECRET = your-webhook-secret-from-mercadopago
If no webhook secret is configured, signature validation is skipped and validated will be false. Always configure webhook secrets in production.
Webhook Topics
Common webhook topics you’ll receive:
Topic Description Resource Type paymentPayment status changed Payment ID merchant_orderOrder status changed Order ID subscriptionSubscription event Subscription ID chargebacksChargeback occurred Chargeback ID
Handling different topics
use Fitodac\LaravelMercadoPago\Services\ WebhookService ;
use Fitodac\LaravelMercadoPago\Services\ PaymentService ;
$data = app ( WebhookService :: class ) -> handle ( $request );
match ( $data [ 'topic' ]) {
'payment' => $this -> processPaymentUpdate ( $data [ 'resource' ]),
'merchant_order' => $this -> processOrderUpdate ( $data [ 'resource' ]),
'subscription' => $this -> processSubscriptionUpdate ( $data [ 'resource' ]),
default => \ Log :: info ( 'Unhandled webhook' , $data ),
};
Common Patterns
Update payment status from webhook
use Fitodac\LaravelMercadoPago\Services\ WebhookService ;
use Fitodac\LaravelMercadoPago\Services\ PaymentService ;
use App\Models\ Order ;
public function handleWebhook ( Request $request )
{
$data = app ( WebhookService :: class ) -> handle ( $request );
if ( $data [ 'topic' ] === 'payment' ) {
// Fetch payment details
$payment = app ( PaymentService :: class ) -> get ( $data [ 'resource' ]);
// Find order by external reference
$externalRef = data_get ( $payment , 'external_reference' );
$order = Order :: where ( 'reference' , $externalRef ) -> first ();
if ( $order ) {
$order -> update ([
'status' => data_get ( $payment , 'status' ),
'mercadopago_payment_id' => data_get ( $payment , 'id' ),
]);
}
}
return response () -> json ([ 'status' => 'ok' ]);
}
Idempotent webhook processing
Prevent duplicate processing:
use Fitodac\LaravelMercadoPago\Services\ WebhookService ;
use App\Models\ WebhookEvent ;
public function handleWebhook ( Request $request )
{
$data = app ( WebhookService :: class ) -> handle ( $request );
// Check if already processed
$exists = WebhookEvent :: where ([
'topic' => $data [ 'topic' ],
'resource_id' => $data [ 'resource' ],
]) -> exists ();
if ( $exists ) {
\ Log :: info ( 'Duplicate webhook ignored' , $data );
return response () -> json ([ 'status' => 'duplicate' ]);
}
// Store and process
WebhookEvent :: create ([
'topic' => $data [ 'topic' ],
'resource_id' => $data [ 'resource' ],
'payload' => $data [ 'payload' ],
'validated' => $data [ 'validated' ],
'processed_at' => now (),
]);
// Process event
$this -> processWebhookEvent ( $data );
return response () -> json ([ 'status' => 'processed' ]);
}
Async webhook processing
Queue webhook processing for better performance:
use Fitodac\LaravelMercadoPago\Services\ WebhookService ;
use App\Jobs\ ProcessMercadoPagoWebhook ;
public function handleWebhook ( Request $request )
{
try {
$data = app ( WebhookService :: class ) -> handle ( $request );
// Validate signature before queueing
if ( ! $data [ 'validated' ]) {
\ Log :: warning ( 'Unvalidated webhook received' );
}
// Dispatch to queue
ProcessMercadoPagoWebhook :: dispatch ( $data );
// Respond immediately
return response () -> json ([ 'status' => 'queued' ], 202 );
} catch ( \ Exception $e ) {
\ Log :: error ( 'Webhook handling failed' , [
'error' => $e -> getMessage (),
]);
return response () -> json ([ 'error' => 'Processing failed' ], 500 );
}
}
Log all webhooks
Maintain webhook audit trail:
use Fitodac\LaravelMercadoPago\Services\ WebhookService ;
public function handleWebhook ( Request $request )
{
try {
$data = app ( WebhookService :: class ) -> handle ( $request );
\ Log :: channel ( 'mercadopago' ) -> info ( 'Webhook received' , [
'topic' => $data [ 'topic' ],
'resource' => $data [ 'resource' ],
'validated' => $data [ 'validated' ],
'ip' => $request -> ip (),
'timestamp' => now () -> toIso8601String (),
]);
// Process webhook
// ...
} catch ( InvalidWebhookSignatureException $e ) {
\ Log :: channel ( 'mercadopago' ) -> error ( 'Invalid webhook signature' , [
'ip' => $request -> ip (),
'error' => $e -> getMessage (),
]);
return response () -> json ([ 'error' => 'Invalid signature' ], 401 );
}
}
Security Considerations
Always validate webhook signatures in production. Without validation, malicious actors could send fake webhooks to manipulate your application.
Best practices
Configure webhook secret : Set MERCADOPAGO_WEBHOOK_SECRET in production
Reject unvalidated webhooks : Return 401 for signature failures
Verify resource existence : Fetch payment/order details from MercadoPago API
Implement idempotency : Track processed webhooks to prevent duplicates
Log security events : Monitor for validation failures
Use HTTPS : MercadoPago requires HTTPS endpoints
Rate limiting : Protect webhook endpoints from abuse
Example security implementation
use Fitodac\LaravelMercadoPago\Services\ WebhookService ;
use Fitodac\LaravelMercadoPago\Exceptions\ InvalidWebhookSignatureException ;
public function handleWebhook ( Request $request )
{
try {
$data = app ( WebhookService :: class ) -> handle ( $request );
// Enforce validation in production
if ( app () -> environment ( 'production' ) && ! $data [ 'validated' ]) {
\ Log :: error ( 'Production webhook without validation' );
return response () -> json ([ 'error' => 'Signature required' ], 401 );
}
// Process webhook
// ...
} catch ( InvalidWebhookSignatureException $e ) {
// Log security event
\ Log :: warning ( 'Webhook signature validation failed' , [
'ip' => $request -> ip (),
'user_agent' => $request -> userAgent (),
'timestamp' => now (),
]);
return response () -> json ([ 'error' => 'Invalid signature' ], 401 );
}
}
Error Handling
InvalidWebhookSignatureException
Thrown when:
Malformed header : Signature header missing ts or v1 components (src/Services/WebhookService.php:75-76)
Signature mismatch : Computed signature doesn’t match provided signature (src/Services/WebhookService.php:57-59)
Error handling example
use Fitodac\LaravelMercadoPago\Services\ WebhookService ;
use Fitodac\LaravelMercadoPago\Exceptions\ InvalidWebhookSignatureException ;
try {
$data = app ( WebhookService :: class ) -> handle ( $request );
// Process webhook
$this -> processWebhook ( $data );
return response () -> json ([ 'status' => 'processed' ]);
} catch ( InvalidWebhookSignatureException $e ) {
// Log security event
\ Log :: warning ( 'Invalid webhook signature' , [
'message' => $e -> getMessage (),
'ip' => $request -> ip (),
]);
return response () -> json ([ 'error' => 'Invalid signature' ], 401 );
} catch ( \ Exception $e ) {
// Log processing error
\ Log :: error ( 'Webhook processing failed' , [
'error' => $e -> getMessage (),
'trace' => $e -> getTraceAsString (),
]);
return response () -> json ([ 'error' => 'Processing failed' ], 500 );
}
PaymentService Retrieve payment details from webhooks
PreferenceService Link webhooks to preferences
Additional Resources
Handling Webhooks Guide Complete guide to webhook integration
MercadoPago Webhook Documentation Official webhook documentation