Webhooks — Notificaciones de eventos
Wepago notifica a tu servidor cuando ocurre un evento importante (cobro aprobado, rechazado, etc.) mediante un HTTP POST a la URL que configuraste en tu cuenta.
Configuración
En comercios.wepago.com → Configuración → API registra:
| Campo | Descripción |
|---|---|
Webhook URL | Endpoint HTTPS de tu servidor que recibirá los eventos |
Webhook Secret | Clave aleatoria (mín. 32 chars) para verificar la firma |
HTTPS obligatorio
Wepago solo envía webhooks a URLs con https://. Endpoints HTTP son rechazados.
Formato del payload
{
"event": "subscription.charge.approved",
"data": {
"subscriptionTransactionId": "01JR9H0BKMCAZ6PY1GR65B91J6",
"subscriptionPlanId": "01JR9H0BKMCAZ6PY1GR65B91J7",
"externalReference": "tu-referencia-interna",
"transactionId": "1200969-1775447756-67089",
"amount": 49900,
"status": "APPROVED",
"chargeDate": 1712534400000
},
"timestamp": 1712534401234
}Eventos disponibles
| Evento | Cuándo ocurre |
|---|---|
subscription.charge.approved | Cobro procesado y aprobado por el banco |
subscription.charge.declined | Cobro rechazado (fondos insuficientes, tarjeta bloqueada, etc.) |
subscription.charge.error | Error técnico durante el cobro |
subscription.charge.voided | Cobro anulado |
Próximamente
subscription.activated y subscription.cancelled están en roadmap.
Verificar la firma
Cada webhook incluye el header X-Webhook-Signature con una firma HMAC-SHA256 del cuerpo del request. Siempre verifica esta firma antes de procesar el evento.
Node.js
const crypto = require('crypto')
function verifyWebhookSignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody) // rawBody = string sin parsear
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
)
}
// Express
app.post('/webhooks/wepago', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature']
const isValid = verifyWebhookSignature(req.body, signature, process.env.WEPAGO_WEBHOOK_SECRET)
if (!isValid) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body)
switch (event.event) {
case 'subscription.charge.approved':
// Activar servicio, enviar confirmación al usuario, etc.
await activateSubscription(event.data.externalReference)
break
case 'subscription.charge.declined':
// Notificar al usuario, reintentar cobro, etc.
await handleDeclinedPayment(event.data.externalReference)
break
}
res.status(200).json({ received: true })
})Python
import hmac, hashlib
def verify_signature(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)PHP
function verifySignature(string $rawBody, string $signature, string $secret): bool {
$expected = hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $signature);
}No uses el body parseado
La firma se calcula sobre el body como string, antes de parsear JSON. Si usas el objeto JSON ya parseado, obtendrás una firma diferente.
Responder correctamente
Tu endpoint debe responder con HTTP 200 en menos de 10 segundos. Wepago no reintenta si el endpoint falla o tarda más.
// ✅ Responde rápido, procesa en background si es necesario
app.post('/webhooks/wepago', async (req, res) => {
res.status(200).json({ received: true }) // Responde PRIMERO
await processEvent(req.body) // Luego procesa
})Polling como alternativa
Si tu servidor no puede recibir webhooks (firewalls, desarrollo local), puedes consultar el estado de un plan directamente:
GET https://merchant.api.wepago.com/merchants/plans/{subscriptionPlanId}/status
Authorization: Bearer {api_key}:{api_secret}Ver Referencia API → Polling de estado.
Probar webhooks en desarrollo
Usa webhook.site o ngrok para exponer tu servidor local:
# ngrok
ngrok http 3000
# → https://abc123.ngrok.io
# Registra https://abc123.ngrok.io/webhooks/wepago como tu Webhook URLURL pública con token en la URL
En el pasado, algunas integraciones usaban una URL con token en el path (ej: /webhook/{token}) como "autenticación". No hagas esto: el token queda en logs de servidores y proxies. Usa siempre la verificación HMAC con el header X-Webhook-Signature.