Understanding API Credentials
Your API credentials are the foundation of secure communication with 88Pay. They consist of two components that work together to authenticate your requests.
API Key Long-lived secret key used to generate tokens Format: sk_test_... or sk_live_...
Merchant ID Your unique identifier in the 88Pay system Format: MCH-{COUNTRY}-{ID}
These credentials are highly sensitive . Treat them like passwords—never expose them publicly or commit them to version control.
Obtaining Your Credentials
Step-by-Step Process
Create Account
Sign up at dash.88pay.io/signup
Provide business information
Verify email address
Set up password (use a strong, unique password)
Complete KYC Verification
Navigate to Account Details in the dashboard Required documents:
Government-issued ID (passport, national ID, driver’s license)
Proof of address (utility bill, bank statement < 3 months)
Business registration (if applicable)
Document approval typically takes 24-48 hours . You’ll receive an email notification when approved.
Access Credentials
Once approved, navigate to Settings → API Credentials You’ll see two sets of credentials: Sandbox (Testing)
Production (Live)
API Key: sk_test_a1b2c3d4e5f6g7h8i9j0
Merchant ID: MCH-COL-TEST-29ZTP4
Base URL: https://api-sandbox.88pay.io
Use for development and testing
No real money transferred
Separate from production credentials
API Key: sk_live_k1l2m3n4o5p6q7r8s9t0
Merchant ID: MCH-COL-29ZTP4
Base URL: https://api.88pay.io
Real transactions
Requires approved KYC
Different credentials than sandbox
Copy & Store Securely
Click the copy icon to copy credentials to clipboard Immediately store them in:
Password manager (1Password, LastPass, Bitwarden)
Environment variables (.env file)
Secret management system (AWS Secrets Manager, HashiCorp Vault)
The API Key is shown only once during creation. If you lose it, you’ll need to regenerate a new one.
API Key Structure
# Sandbox
sk_test_ {32_character_random_string}
# Production
sk_live_ {32_character_random_string}
Characteristics:
Always starts with sk_test_ (sandbox) or sk_live_ (production)
Followed by 32 alphanumeric characters
Case-sensitive
No expiration (unless manually rotated)
Merchant ID Structure
MCH- {COUNTRY} - {UNIQUE_ID}
# Examples:
MCH-COL-29ZTP4 # Colombia
MCH-MEX-8K2PQ1 # Mexico
MCH-BRA-7N4WX9 # Brazil
MCH-COL-TEST-29ZTP4 # Sandbox (includes "TEST")
Components:
MCH: Merchant prefix (constant)
{COUNTRY}: 3-letter country code (COL, MEX, BRA, etc.)
{UNIQUE_ID}: Random identifier (6 characters)
Sandbox includes -TEST- segment
Storing Credentials Securely
Environment Variables (Recommended)
Create .env file: # .env
EIGHTY_EIGHT_PAY_API_KEY = sk_test_a1b2c3d4e5f6g7h8i9j0
EIGHTY_EIGHT_PAY_MERCHANT_ID = MCH-COL-TEST-29ZTP4
EIGHTY_EIGHT_PAY_BASE_URL = https://api-sandbox.88pay.io
Load with dotenv: // Load at app startup
require ( 'dotenv' ). config ();
const apiKey = process . env . EIGHTY_EIGHT_PAY_API_KEY ;
const merchantId = process . env . EIGHTY_EIGHT_PAY_MERCHANT_ID ;
const baseUrl = process . env . EIGHTY_EIGHT_PAY_BASE_URL ;
Add to .gitignore: .env
.env.*
!.env.example
Create .env file: # .env
EIGHTY_EIGHT_PAY_API_KEY = sk_test_a1b2c3d4e5f6g7h8i9j0
EIGHTY_EIGHT_PAY_MERCHANT_ID = MCH-COL-TEST-29ZTP4
EIGHTY_EIGHT_PAY_BASE_URL = https://api-sandbox.88pay.io
Load with python-dotenv: import os
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
api_key = os.getenv( 'EIGHTY_EIGHT_PAY_API_KEY' )
merchant_id = os.getenv( 'EIGHTY_EIGHT_PAY_MERCHANT_ID' )
base_url = os.getenv( 'EIGHTY_EIGHT_PAY_BASE_URL' )
Create .env file: # .env
EIGHTY_EIGHT_PAY_API_KEY = sk_test_a1b2c3d4e5f6g7h8i9j0
EIGHTY_EIGHT_PAY_MERCHANT_ID = MCH-COL-TEST-29ZTP4
EIGHTY_EIGHT_PAY_BASE_URL = https://api-sandbox.88pay.io
Load with vlucas/phpdotenv: <? php
require 'vendor/autoload.php' ;
$dotenv = Dotenv\ Dotenv :: createImmutable ( __DIR__ );
$dotenv -> load ();
$apiKey = $_ENV [ 'EIGHTY_EIGHT_PAY_API_KEY' ];
$merchantId = $_ENV [ 'EIGHTY_EIGHT_PAY_MERCHANT_ID' ];
$baseUrl = $_ENV [ 'EIGHTY_EIGHT_PAY_BASE_URL' ];
?>
Cloud Secret Managers
# Store secret
aws secretsmanager create-secret \
--name 88pay/api-credentials \
--secret-string '{
"api_key": "sk_live_...",
"merchant_id": "MCH-COL-..."
}'
# Retrieve in application
aws secretsmanager get-secret-value \
--secret-id 88pay/api-credentials
Node.js SDK: const { SecretsManagerClient , GetSecretValueCommand } = require ( "@aws-sdk/client-secrets-manager" );
const client = new SecretsManagerClient ({ region: "us-east-1" });
const response = await client . send (
new GetSecretValueCommand ({ SecretId: "88pay/api-credentials" })
);
const credentials = JSON . parse ( response . SecretString );
Google Cloud Secret Manager
# Store secret
echo -n "sk_live_..." | gcloud secrets create 88pay-api-key --data-file=-
echo -n "MCH-COL-..." | gcloud secrets create 88pay-merchant-id --data-file=-
# Retrieve in application
gcloud secrets versions access latest --secret= "88pay-api-key"
# Store secret
vault kv put secret/88pay \
api_key="sk_live_..." \
merchant_id="MCH-COL-..."
# Retrieve in application
vault kv get -field=api_key secret/88pay
# Store secret
az keyvault secret set \
--vault-name my-vault \
--name 88pay-api-key \
--value "sk_live_..."
# Retrieve in application
az keyvault secret show \
--vault-name my-vault \
--name 88pay-api-key
Security Best Practices
Never Hardcode
Server-Side Only
Separate Environments
Limit Access
❌ Don’t do this: // Hardcoded credentials (EXPOSED!)
const API_KEY = 'sk_live_abc123...' ;
const MERCHANT_ID = 'MCH-COL-29ZTP4' ;
✅ Do this instead: // Load from environment variables
const API_KEY = process . env . EIGHTY_EIGHT_PAY_API_KEY ;
const MERCHANT_ID = process . env . EIGHTY_EIGHT_PAY_MERCHANT_ID ;
Hardcoded credentials can be:
Committed to Git repositories
Exposed in error logs
Found in browser DevTools
Leaked through code screenshots
❌ Don’t do this: // Client-side code (EXPOSED in browser!)
fetch ( 'https://api.88pay.io/api/transactions/charges' , {
headers: {
'x-api-key' : apiKey , // VISIBLE IN DEVTOOLS!
'x-merchant-id' : merchantId
}
});
✅ Do this instead: // Client calls your backend
const response = await fetch ( '/api/create-payment' , {
method: 'POST' ,
body: JSON . stringify ({ amount: 50000 })
});
// Your backend handles 88Pay authentication
Backend endpoint: // /api/create-payment (server-side)
export default async function handler ( req , res ) {
const token = await get88PayToken (); // Server-side only
const payment = await fetch ( 'https://api.88pay.io/...' , {
headers: {
'x-api-key' : process . env . EIGHTY_EIGHT_PAY_API_KEY
}
});
res . json ( await payment . json ());
}
Use different credentials per environment: Environment Credentials Base URL Development Sandbox https://api-sandbox.88pay.ioStaging Sandbox https://api-sandbox.88pay.ioProduction Production https://api.88pay.io
Configuration example: # .env.development
EIGHTY_EIGHT_PAY_API_KEY = sk_test_dev123...
EIGHTY_EIGHT_PAY_MERCHANT_ID = MCH-COL-TEST-ABC
EIGHTY_EIGHT_PAY_BASE_URL = https://api-sandbox.88pay.io
# .env.production
EIGHTY_EIGHT_PAY_API_KEY = sk_live_prod456...
EIGHTY_EIGHT_PAY_MERCHANT_ID = MCH-COL-XYZ
EIGHTY_EIGHT_PAY_BASE_URL = https://api.88pay.io
Never mix environments! Using production credentials in development can cause real charges.
Access control matrix: Role Should Have Access Reason ✅ Backend Developers Yes Need to implement integration ✅ DevOps Engineers Yes Deploy and manage secrets ✅ CTO/Tech Leads Yes Security oversight ❌ Frontend Developers No Work only with public APIs ❌ Designers No No technical need ❌ Customer Support No Access not required for duties ❌ Marketing Team No No technical involvement
Best practices:
Use need-to-know basis principle
Implement role-based access control (RBAC)
Audit credential access logs quarterly
Revoke access immediately when team members leave
Additional Security Measures
For production environments:
Navigate to Settings → Security in dashboard
Add your server IPs (IPv4 and IPv6)
API will reject requests from non-whitelisted IPs
Example: Whitelisted IPs:
- 203.0.113.10 (Production Server 1)
- 203.0.113.11 (Production Server 2)
- 2001:db8::1 (IPv6 Server)
Update IP whitelist when scaling infrastructure or changing hosting providers.
Set up alerts for:
Unexpected spike in API calls
Failed authentication attempts (401/403)
Requests from unknown IPs
Unusual transaction patterns
Integration examples: const Sentry = require ( "@sentry/node" );
// Track failed auth attempts
if ( response . status === 401 ) {
Sentry . captureMessage ( '88Pay Auth Failed' , {
level: 'warning' ,
tags: { service: '88pay' }
});
}
Implement Request Logging
Log these fields (but NOT credentials): {
timestamp : '2025-01-17T14:30:00Z' ,
endpoint : '/api/transactions/charges' ,
method : 'POST' ,
status_code : 200 ,
response_time_ms : 342 ,
merchant_id : 'MCH-COL-29ZTP4' , // OK to log
transaction_ref : 'IP9BD7CJES6' , // OK to log
// api_key: '...' ⚠️ NEVER LOG THIS!
// access_token: '...' ⚠️ NEVER LOG THIS!
}
Never log:
API Keys
Access Tokens
Customer card numbers
CVV codes
Full bank account numbers
All communications must use HTTPS:
✅ API requests: https://api.88pay.io
✅ Webhook URLs: https://yoursite.com/webhook
✅ Return URLs: https://yoursite.com/payment-success
❌ HTTP requests will be rejected
Verify SSL certificates: # Check certificate validity
openssl s_client -connect api.88pay.io:443 -servername api.88pay.io
Rotating Credentials
When to Rotate
Scheduled Rotation (Every 90 Days)
Proactive security measure Set a recurring calendar reminder to rotate keys quarterly. This limits exposure if a key is compromised without your knowledge.
Immediate rotation required if:
Key accidentally committed to Git
Shared with unauthorized person
Found in logs or error messages
Employee with access leaves company
Suspicious API activity detected
After any security audit or penetration test that flags credential management issues.
Rotation Process
Generate New Key
In the dashboard, navigate to Settings → API Credentials Click “Regenerate API Key” This will generate a new key. The old key will remain active for 24 hours.
Update Application
Update environment variables in all environments: # Development
EIGHTY_EIGHT_PAY_API_KEY = sk_live_NEW_KEY_HERE
# Staging
EIGHTY_EIGHT_PAY_API_KEY = sk_live_NEW_KEY_HERE
# Production (deploy carefully!)
EIGHTY_EIGHT_PAY_API_KEY = sk_live_NEW_KEY_HERE
Deploy Changes
Deploy updated configuration to all servers Zero-downtime deployment:
Both old and new keys work during transition
Rolling deployment ensures no downtime
Old key deactivates after 24 hours
Verify & Deactivate
Monitor for any errors using old key After 24 hours, old key is automatically deactivated If issues arise, you can manually deactivate new key and revert
Testing Your Credentials
Quick Verification
Test that your credentials work:
# Test authentication
curl -X POST "https://api-sandbox.88pay.io/api/auth/token" \
-H "x-api-key: YOUR_API_KEY" \
-H "x-merchant-id: YOUR_MERCHANT_ID"
# Expected response:
# { "status": "Success", "code": 200, "data": { "access_token": "...", ... } }
Common Errors
{ "code" : 401 , "message" : "Invalid API key or merchant ID" }
Causes:
Typo in API Key or Merchant ID
Using sandbox key with production endpoint (or vice versa)
Extra whitespace in credentials
Key has been rotated/deactivated
Fix:
Copy credentials directly from dashboard
Verify environment matches (sandbox vs production)
Check for hidden characters: apiKey.trim()
{ "code" : 403 , "message" : "Access denied" }
Causes:
Account not yet approved
Account suspended
IP not whitelisted (if enabled)
Fix: Contact support@88pay.io
Credential Checklist
Before moving to the next guide, verify:
Account created and email verified
KYC documents uploaded and approved
Sandbox credentials copied from dashboard
Credentials stored in environment variables (not hardcoded)
.env added to .gitignore
Credentials tested successfully
Production credentials obtained (when ready for go-live)
Next Steps