Credential Management
Laravel MercadoPago uses a flexible credential resolution system that separates credential retrieval from SDK configuration. This enables support for environment-based credentials, database storage, secrets managers, and multi-tenant configurations.
Credential Types
Mercado Pago uses three types of credentials:
Access Token Required - Authenticates API requests to Mercado Pago
Public Key Optional - Used for client-side SDK initialization
Webhook Secret Optional - Validates webhook signatures (HMAC-SHA256)
Access Token
Purpose : Server-side authentication for all API operations
Required for : All services except WebhookService (when not validating signatures)
Format : Long alphanumeric string (e.g., APP_USR-123456789-abcdef-...)
The access token provides full access to your Mercado Pago account. Never commit it to version control or expose it in client-side code.
Public Key
Purpose : Client-side SDK initialization for tokenizing cards
Required for : Frontend integrations using Mercado Pago SDK.js
Format : Public key string (e.g., APP_USR-abcdef123456-...)
The public key can be safely exposed in client-side code. It only allows card tokenization, not API operations.
Webhook Secret
Purpose : HMAC-SHA256 signature validation for incoming webhooks
Required for : Secure webhook validation
Format : Secret string provided in your Mercado Pago dashboard
Always configure webhook secrets in production to prevent unauthorized webhook requests.
Credential Resolution
MercadoPagoCredentials DTO
Credentials are encapsulated in a readonly Data Transfer Object:
Location : src/DTO/MercadoPagoCredentials.php:7
final readonly class MercadoPagoCredentials
{
public function __construct (
public string $accessToken ,
public ? string $publicKey = null ,
public ? string $webhookSecret = null ,
) {}
public function toArray () : array
{
return [
'access_token' => $this -> accessToken ,
'public_key' => $this -> publicKey ,
'webhook_secret' => $this -> webhookSecret ,
];
}
}
Benefits of using a DTO :
Type Safety - Access token is guaranteed to be a string
Immutability - Credentials cannot be modified after creation
Clarity - Public and webhook secret are explicitly optional
Serialization - Easy conversion to array format
CredentialResolverInterface
The credential resolver contract defines how credentials are retrieved:
Location : src/Contracts/CredentialResolverInterface.php:9
interface CredentialResolverInterface
{
public function resolve () : MercadoPagoCredentials ;
}
This interface allows you to implement custom credential resolution strategies while maintaining compatibility with the package.
EnvCredentialResolver
The default implementation reads credentials from Laravel’s configuration:
Location : src/Support/EnvCredentialResolver.php:12
final readonly class EnvCredentialResolver implements CredentialResolverInterface
{
public function __construct (
private Repository $config ,
) {}
public function resolve () : MercadoPagoCredentials
{
$accessToken = ( string ) $this -> config -> get ( 'mercadopago.access_token' , '' );
if ( $accessToken === '' ) {
throw MercadoPagoConfigurationException :: missingAccessToken ();
}
$publicKey = $this -> config -> get ( 'mercadopago.public_key' );
$webhookSecret = $this -> config -> get ( 'mercadopago.webhook_secret' );
return new MercadoPagoCredentials (
accessToken : $accessToken ,
publicKey : is_string ( $publicKey ) && $publicKey !== '' ? $publicKey : null ,
webhookSecret : is_string ( $webhookSecret ) && $webhookSecret !== '' ? $webhookSecret : null ,
);
}
}
Key behaviors :
Access Token Validation (src/Support/EnvCredentialResolver.php:22) - Throws MercadoPagoConfigurationException if missing
Optional Credentials (src/Support/EnvCredentialResolver.php:26) - Public key and webhook secret default to null if empty
Type Coercion (src/Support/EnvCredentialResolver.php:31) - Ensures only non-empty strings are passed
Configuration
Environment Variables
The default configuration reads from .env:
# Required: Access token for API authentication
MERCADOPAGO_ACCESS_TOKEN =APP_USR-123456789-abcdef-...
# Optional: Public key for client-side SDK
MERCADOPAGO_PUBLIC_KEY =APP_USR-abcdef123456-...
# Optional: Webhook secret for signature validation
MERCADOPAGO_WEBHOOK_SECRET =your_webhook_secret_here
# Optional: Route prefix for demo endpoints
MERCADOPAGO_ROUTE_PREFIX =api/mercadopago
# Optional: Enable/disable demo routes
MERCADOPAGO_ENABLE_DEMO_ROUTES =true
# Optional: Runtime environment (local, server)
MERCADOPAGO_RUNTIME_ENVIRONMENT =local
Configuration File
Location : config/mercadopago.php:5
return [
'access_token' => env ( 'MERCADOPAGO_ACCESS_TOKEN' ),
'public_key' => env ( 'MERCADOPAGO_PUBLIC_KEY' ),
'webhook_secret' => env ( 'MERCADOPAGO_WEBHOOK_SECRET' ),
'route_prefix' => env ( 'MERCADOPAGO_ROUTE_PREFIX' , 'api/mercadopago' ),
'enable_demo_routes' => ( bool ) env ( 'MERCADOPAGO_ENABLE_DEMO_ROUTES' , true ),
'runtime_environment' => env ( 'MERCADOPAGO_RUNTIME_ENVIRONMENT' ),
];
Why publish the configuration?
Publishing the configuration file allows you to:
Add custom configuration keys
Modify default values
Document configuration options for your team
Override configuration in tests
php artisan vendor:publish --tag=mercadopago-config
The published file will be located at config/mercadopago.php in your Laravel project.
SDK Configuration
The MercadoPagoClientFactory handles SDK configuration using resolved credentials:
Location : src/Support/MercadoPagoClientFactory.php:45
private function configureSdk () : void
{
$sdkConfigClass = 'MercadoPago \\ MercadoPagoConfig' ;
if ( ! class_exists ( $sdkConfigClass )) {
throw MercadoPagoConfigurationException :: sdkNotInstalled ();
}
$credentials = $this -> credentialResolver -> resolve ();
$sdkConfigClass :: setAccessToken ( $credentials -> accessToken );
$runtimeEnvironment = $this -> config -> get ( 'mercadopago.runtime_environment' );
if ( ! is_string ( $runtimeEnvironment ) || $runtimeEnvironment === '' ) {
$runtimeEnvironment = app () -> environment ([ 'local' , 'testing' ]) ? 'local' : 'server' ;
}
foreach ([ 'setRuntimeEnvironment' , 'setRuntimeEnviroment' ] as $methodName ) {
if ( method_exists ( $sdkConfigClass , $methodName )) {
$sdkConfigClass :: { $methodName }( $runtimeEnvironment );
break ;
}
}
}
Configuration steps :
SDK Validation - Verifies Mercado Pago SDK is installed
Credential Resolution - Calls the credential resolver
Access Token - Sets the access token globally on MercadoPagoConfig
Runtime Environment - Configures SDK for local development or production
Method Compatibility - Handles SDK version differences in method names
SDK configuration happens automatically on the first service call. You don’t need to manually configure the SDK.
Runtime Environment
The runtime environment affects how the Mercado Pago SDK reports telemetry:
local - Development/testing environment
server - Production environment
Auto-detection (src/Support/MercadoPagoClientFactory.php:60):
$runtimeEnvironment = app () -> environment ([ 'local' , 'testing' ]) ? 'local' : 'server' ;
Manual override :
MERCADOPAGO_RUNTIME_ENVIRONMENT =server
Custom Credential Resolvers
Database-Backed Credentials
For multi-tenant applications or centralized credential management:
namespace App\Services ;
use Fitodac\LaravelMercadoPago\Contracts\ CredentialResolverInterface ;
use Fitodac\LaravelMercadoPago\DTO\ MercadoPagoCredentials ;
use Fitodac\LaravelMercadoPago\Exceptions\ MercadoPagoConfigurationException ;
use App\Models\ PaymentCredential ;
class DatabaseCredentialResolver implements CredentialResolverInterface
{
public function resolve () : MercadoPagoCredentials
{
$credential = PaymentCredential :: where ( 'provider' , 'mercadopago' )
-> where ( 'environment' , app () -> environment ())
-> first ();
if ( ! $credential ) {
throw MercadoPagoConfigurationException :: missingAccessToken ();
}
return new MercadoPagoCredentials (
accessToken : decrypt ( $credential -> access_token ),
publicKey : $credential -> public_key ? decrypt ( $credential -> public_key ) : null ,
webhookSecret : $credential -> webhook_secret ? decrypt ( $credential -> webhook_secret ) : null ,
);
}
}
Register in AppServiceProvider :
use Fitodac\LaravelMercadoPago\Contracts\ CredentialResolverInterface ;
use App\Services\ DatabaseCredentialResolver ;
public function register ()
{
$this -> app -> bind (
CredentialResolverInterface :: class ,
DatabaseCredentialResolver :: class
);
}
Vault/Secrets Manager Integration
For AWS Secrets Manager, HashiCorp Vault, or similar:
namespace App\Services ;
use Fitodac\LaravelMercadoPago\Contracts\ CredentialResolverInterface ;
use Fitodac\LaravelMercadoPago\DTO\ MercadoPagoCredentials ;
use Aws\SecretsManager\ SecretsManagerClient ;
class VaultCredentialResolver implements CredentialResolverInterface
{
public function __construct (
private SecretsManagerClient $secretsManager
) {}
public function resolve () : MercadoPagoCredentials
{
$result = $this -> secretsManager -> getSecretValue ([
'SecretId' => 'mercadopago/credentials' ,
]);
$credentials = json_decode ( $result [ 'SecretString' ], true );
return new MercadoPagoCredentials (
accessToken : $credentials [ 'access_token' ],
publicKey : $credentials [ 'public_key' ] ?? null ,
webhookSecret : $credentials [ 'webhook_secret' ] ?? null ,
);
}
}
Multi-Tenant Resolver
For applications serving multiple merchants:
namespace App\Services ;
use Fitodac\LaravelMercadoPago\Contracts\ CredentialResolverInterface ;
use Fitodac\LaravelMercadoPago\DTO\ MercadoPagoCredentials ;
class TenantCredentialResolver implements CredentialResolverInterface
{
public function resolve () : MercadoPagoCredentials
{
$tenant = tenant (); // Your tenant resolution logic
return new MercadoPagoCredentials (
accessToken : $tenant -> mercadopago_access_token ,
publicKey : $tenant -> mercadopago_public_key ,
webhookSecret : $tenant -> mercadopago_webhook_secret ,
);
}
}
In multi-tenant scenarios, ensure services are registered as scoped or transient, not singletons, to prevent credential leakage between tenants.
Security Considerations
Never Commit Credentials
Add to .gitignore:
.env
.env.production
config/mercadopago.php # If you hardcode values
Encrypt Stored Credentials
When storing in database:
// Before storing
$credential -> access_token = encrypt ( $accessToken );
// When resolving
$accessToken = decrypt ( $credential -> access_token );
Use Environment-Specific Credentials
Never use production credentials in development:
# .env.local
MERCADOPAGO_ACCESS_TOKEN =TEST-123456789-...
# .env.production
MERCADOPAGO_ACCESS_TOKEN =APP_USR-123456789-...
Rotate Credentials Regularly
Implement credential rotation:
class RotateCredentialsCommand extends Command
{
public function handle ()
{
$newCredentials = $this -> fetchFromMercadoPago ();
config ([
'mercadopago.access_token' => $newCredentials -> accessToken ,
'mercadopago.public_key' => $newCredentials -> publicKey ,
]);
// Clear SDK configuration to force re-initialization
app () -> forgetInstance ( MercadoPagoClientFactory :: class );
}
}
Validate Webhook Signatures
Always configure webhook secrets in production:
// WebhookService automatically validates if secret is configured
$webhook = $webhookService -> handle ( $request );
if ( ! $webhook [ 'validated' ]) {
\ Log :: warning ( 'Webhook received without signature validation' );
// Consider rejecting in production
}
Validation implementation (src/Services/WebhookService.php:38):
private function assertValidSignature (
Request $request ,
array $payload ,
string $secret ,
string $signatureHeader ,
) : void {
$signatureParts = $this -> parseSignatureHeader ( $signatureHeader );
$resourceId = ( string ) ( $request -> query ( 'data.id' ) ?? Arr :: get ( $payload , 'data.id' , '' ));
$requestId = ( string ) $request -> header ( 'x-request-id' , '' );
$manifest = sprintf (
'id:%s;request-id:%s;ts:%s;' ,
$resourceId ,
$requestId ,
$signatureParts [ 'ts' ],
);
$computedHash = hash_hmac ( 'sha256' , $manifest , $secret );
if ( ! hash_equals ( $computedHash , $signatureParts [ 'v1' ])) {
throw InvalidWebhookSignatureException :: signatureMismatch ();
}
}
Limit Credential Access
Use Laravel’s authorization:
Gate :: define ( 'view-mercadopago-credentials' , function ( User $user ) {
return $user -> hasRole ( 'admin' );
});
if ( Gate :: allows ( 'view-mercadopago-credentials' )) {
return config ( 'mercadopago.access_token' );
}
Testing
Mock Credentials in Tests
use Fitodac\LaravelMercadoPago\Contracts\ CredentialResolverInterface ;
use Fitodac\LaravelMercadoPago\DTO\ MercadoPagoCredentials ;
class PaymentServiceTest extends TestCase
{
protected function setUp () : void
{
parent :: setUp ();
$this -> app -> bind ( CredentialResolverInterface :: class , function () {
return new class implements CredentialResolverInterface {
public function resolve () : MercadoPagoCredentials
{
return new MercadoPagoCredentials (
accessToken : 'TEST-ACCESS-TOKEN' ,
publicKey : 'TEST-PUBLIC-KEY' ,
webhookSecret : 'TEST-WEBHOOK-SECRET' ,
);
}
};
});
}
}
Override Configuration in Tests
public function test_payment_creation ()
{
config ([
'mercadopago.access_token' => 'TEST-TOKEN' ,
'mercadopago.runtime_environment' => 'local'
]);
$service = app ( PaymentService :: class );
// Test payment creation
}
Troubleshooting
Missing Access Token
Error : MercadoPagoConfigurationException: Mercado Pago access token is not configured.
Solution :
Check .env file has MERCADOPAGO_ACCESS_TOKEN
Verify .env is loaded: php artisan config:clear
Ensure credential resolver is resolving correctly
SDK Not Installed
Error : MercadoPagoConfigurationException: Mercado Pago PHP SDK is not installed.
Solution :
composer require mercadopago/dx-php:^3.8
Invalid Webhook Signature
Error : InvalidWebhookSignatureException: Mercado Pago webhook signature is invalid.
Solution :
Verify MERCADOPAGO_WEBHOOK_SECRET matches Mercado Pago dashboard
Check webhook is coming from Mercado Pago (not a test tool)
Ensure webhook secret hasn’t been rotated
Next Steps
Architecture Understand how credentials flow through the package
Services Learn how services use credentials
Security Production security best practices
Webhooks Implement secure webhook handling