Overview
Wompi sends webhook notifications when transaction states change (approved, declined, etc.). The webhook handler verifies signature integrity, updates order status, and manages inventory.
Endpoint: POST /api/wompi/webhook
Webhook Flow
Webhook Request
Body Parameters
Transaction data container Unique transaction ID from Wompi
data.transaction.reference
Order reference (e.g., KAIU-12345)
data.transaction.amount_in_cents
Transaction amount in cents
Transaction status: APPROVED, DECLINED, VOIDED, ERROR, or PENDING
Integrity signature object HMAC-SHA256 checksum for verification
Unix timestamp of the webhook event
Example Webhook Payload
{
"data" : {
"transaction" : {
"id" : "12345-1677649200-wompi" ,
"reference" : "KAIU-12345" ,
"amount_in_cents" : 50000 ,
"status" : "APPROVED" ,
"customer_email" : "[email protected] " ,
"currency" : "COP"
}
},
"signature" : {
"checksum" : "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
},
"timestamp" : 1677649200
}
Signature Verification
Wompi signs all webhooks with HMAC-SHA256 to ensure authenticity.
const { data , signature , timestamp } = req . body ;
if ( ! data || ! signature || ! timestamp ) {
console . error ( "Webhook inválido: Datos incompletos" );
return res . status ( 400 ). json ({ error: "Datos incompletos" });
}
const { id , reference , amount_in_cents , status } = data . transaction ;
// Verify Integrity
const secret = process . env . WOMPI_INTEGRITY_SECRET ;
if ( ! secret ) return res . status ( 500 ). json ({ error: "Configuración incompleta" });
const integrityString = ` ${ id }${ status }${ amount_in_cents }${ timestamp }${ secret } ` ;
const generatedSignature = crypto . createHash ( 'sha256' ). update ( integrityString ). digest ( 'hex' );
if ( generatedSignature !== signature . checksum ) {
console . error ( "Error Integridad Wompi" );
return res . status ( 400 ). json ({ error: "Integridad fallida" });
}
HMAC-SHA256(transactionId + status + amountInCents + timestamp + integritySecret)
Always verify the signature before processing webhooks to prevent fraudulent transaction updates.
Response Strategy
The webhook handler uses a fire-and-forget pattern to prevent Wompi timeouts.
// 2. Respond IMMEDIATELY to Wompi
res . status ( 200 ). json ({ success: true });
// 3. Process Business Logic Asynchronously
processOrderAsync ( req . body ). catch ( err => {
console . error ( "❌ CRITICAL: Error procesando orden en background:" , err );
});
This pattern responds to Wompi within milliseconds, then processes the order in the background. Works well on persistent servers (Express) but may require queue systems on serverless platforms.
Order Processing
Finding Orders
Orders are looked up by reference (PIN) or external ID:
const pinStr = reference . split ( '-' )[ 1 ]; // KAIU-12345 -> 12345
const pin = parseInt ( pinStr , 10 );
let dbOrder = null ;
if ( ! isNaN ( pin )) {
dbOrder = await prisma . order . findFirst ({
where: { readableId: pin },
include: { items: true }
});
}
// Fallback: Try externalId
if ( ! dbOrder ) {
dbOrder = await prisma . order . findFirst ({
where: { externalId: pinStr },
include: { items: true }
});
}
if ( ! dbOrder ) {
console . warn ( `⚠️ Orden DB no encontrada para referencia ${ reference } ` );
return ;
}
Approved Payments
When status === 'APPROVED':
if ( status === 'APPROVED' ) {
console . log ( "💰 PAGO APROBADO -> Confirmando Orden" );
// 1. Venndelo -> CONFIRMED
await updateVenndeloStatus ( venndeloId , 'CONFIRMED' );
// 2. DB -> STATUS: CONFIRMED
await prisma . order . update ({
where: { id: dbOrder . id },
data: { status: 'CONFIRMED' }
});
// 3. Inventory -> Confirm Sale
await InventoryService . confirmSale ( dbOrder . items );
console . log ( "📦 Inventario actualizado y orden confirmada." );
}
Actions:
Update Venndelo order status to CONFIRMED
Update local database order to CONFIRMED
Deduct items from inventory (convert reservation to sale)
Declined/Failed Payments
When status === 'DECLINED' | 'VOIDED' | 'ERROR':
else if ([ 'DECLINED' , 'VOIDED' , 'ERROR' ]. includes ( status )) {
console . log ( `🚫 PAGO RECHAZADO ( ${ status } ) -> Cancelando Orden` );
// 1. Venndelo -> REJECTED
await updateVenndeloStatus ( venndeloId , 'REJECTED' );
// 2. DB -> STATUS: CANCELLED
await prisma . order . update ({
where: { id: dbOrder . id },
data: { status: 'CANCELLED' }
});
// 3. Inventory -> Release Stock
await InventoryService . releaseReserve ( dbOrder . items );
console . log ( "♻️ Stock liberado." );
}
Actions:
Update Venndelo order status to REJECTED
Update local database order to CANCELLED
Release reserved inventory back to available stock
Venndelo Integration
Orders are synchronized with Venndelo (fulfillment platform):
const updateVenndeloStatus = async ( id , newStatus ) => {
if ( ! id ) return ;
try {
console . log ( `📡 Llamando a Venndelo para actualizar status a ${ newStatus } ...` );
const vRes = await fetch ( `https://api.venndelo.com/v1/admin/orders/ ${ id } /modify-order-confirmation-status` , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'X-Venndelo-Api-Key' : VENNDELO_API_KEY
},
body: JSON . stringify ({ confirmation_status: newStatus })
});
if ( ! vRes . ok ) console . error ( "Error respuesta Venndelo:" , await vRes . text ());
else console . log ( "✅ Venndelo actualizado." );
} catch ( e ) {
console . error ( "Error Network Venndelo:" , e . message );
}
};
Inventory Management
Confirming Sales
// From InventoryService
await InventoryService . confirmSale ( dbOrder . items );
Converts reserved stock to confirmed sales, permanently deducting from inventory.
Releasing Reservations
await InventoryService . releaseReserve ( dbOrder . items );
Returns reserved stock to available inventory when payments fail.
Error Handling
Validation Errors
Scenario Status Response Missing data, signature, or timestamp 400{ "error": "Datos incompletos" }Invalid signature 400{ "error": "Integridad fallida" }Missing WOMPI_INTEGRITY_SECRET 500{ "error": "Configuración incompleta" }
Background Processing Errors
processOrderAsync ( req . body ). catch ( err => {
console . error ( "❌ CRITICAL: Error procesando orden en background:" , err );
});
Errors in background processing are logged but do not affect the webhook response to Wompi.
Monitor logs for CRITICAL errors as these indicate failed order processing despite successful payment.
Webhook Registration
Register your webhook URL in Wompi dashboard:
URL: https://api.kaiunaturalliving.com/api/wompi/webhook
Events to subscribe:
Testing Webhooks
Manual Test
curl -X POST https://api.kaiunaturalliving.com/api/wompi/webhook \
-H "Content-Type: application/json" \
-d '{
"data": {
"transaction": {
"id": "test-12345",
"reference": "KAIU-12345",
"amount_in_cents": 50000,
"status": "APPROVED"
}
},
"signature": {
"checksum": "GENERATE_VALID_SIGNATURE"
},
"timestamp": 1677649200
}'
Generate a valid signature using the formula: SHA256(id + status + amount_in_cents + timestamp + secret)
Test Signature Generator
const crypto = require ( 'crypto' );
const id = 'test-12345' ;
const status = 'APPROVED' ;
const amount = 50000 ;
const timestamp = 1677649200 ;
const secret = process . env . WOMPI_INTEGRITY_SECRET ;
const integrityString = ` ${ id }${ status }${ amount }${ timestamp }${ secret } ` ;
const checksum = crypto . createHash ( 'sha256' ). update ( integrityString ). digest ( 'hex' );
console . log ( 'Checksum:' , checksum );
Environment Variables
Secret key for webhook signature verification
API key for Venndelo fulfillment integration
Monitoring
Key log messages to monitor:
---- WOMPI WEBHOOK RECEIVED ----
💰 PAGO APROBADO - > Confirmando Orden
📡 Llamando a Venndelo para actualizar status a CONFIRMED...
✅ Venndelo actualizado.
📦 Inventario actualizado y orden confirmada.
🚫 PAGO RECHAZADO (DECLINED) - > Cancelando Orden
♻️ Stock liberado.
❌ CRITICAL: Error procesando orden en background: [error details]
Security Best Practices
Always verify signatures before processing
Use HTTPS to prevent man-in-the-middle attacks
Validate transaction amounts against database records
Log all webhook events for audit trails
Implement idempotency to handle duplicate webhooks
Rate limit webhook endpoint to prevent abuse
Next Steps
Wompi Integration Learn about signature generation for checkout
Wompi Documentation Official Wompi webhook documentation