Webhooks
Receive real-time notifications when payment status changes.
Overview
When you create a checkout session with a notification_url, we send webhook notifications to that URL when the payment status changes.
Important
Always verify webhook payloads and respond with HTTP 200 within 30 seconds to acknowledge receipt.
Webhook Payload
All webhooks are sent as POST requests with JSON body:
POST https://your-server.com/webhooks
Content-Type: application/json
X-Signature: a1b2c3d4e5f6...
{
"event": "payment.success",
"payment_id": "pi_abc123xyz",
"checkout_session_id": "cs_abc123xyz",
"merchant_id": "your_merchant_id",
"reference_id": "order_12345",
"status": "success",
"amount": 150.00,
"currency": "BRL",
"method_code": "s-interio-mt-1",
"payment_method": "pix",
"provider": "sfp",
"paid_at": "2025-01-04T12:30:00Z",
"metadata": {
"custom_field": "your_value"
},
"timestamp": "2025-01-04T12:30:01Z"
}Event Types
payment.successPayment completed successfully
payment.failedPayment failed or was declined
payment.pendingPayment is pending (e.g., awaiting PIX confirmation)
payment.processingPayment is being processed
payment.cancelledPayment was cancelled by customer
Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | Event type (see above) |
payment_id | string | Our payment identifier (pi_xxx) |
checkout_session_id | string | Checkout session identifier (cs_xxx) |
merchant_id | string | Your merchant identifier |
reference_id | string | Your order/transaction ID |
status | string | Payment status |
amount | number | Transaction amount |
currency | string | Currency code |
method_code | string | Payment method used |
paid_at | string | ISO 8601 timestamp when paid (success only) |
error_message | string | Error description (failed only) |
payment_method | string | Payment method type (pix, card, boleto, etc.) |
provider | string | Payment provider used (sfp, izipay, tupay, etc.) |
metadata | object | Your custom metadata from checkout session |
timestamp | string | ISO 8601 timestamp of the webhook |
Example: Success Webhook
{
"event": "payment.success",
"payment_id": "pi_abc123xyz",
"checkout_session_id": "cs_abc123xyz",
"merchant_id": "your_merchant_id",
"reference_id": "order_12345",
"status": "success",
"amount": 150.00,
"currency": "BRL",
"method_code": "s-interio-mt-1",
"payment_method": "pix",
"provider": "sfp",
"provider_transaction_id": "sfp_tx_987654",
"paid_at": "2025-01-04T12:30:00Z",
"timestamp": "2025-01-04T12:30:01Z"
}Example: Failed Webhook
{
"event": "payment.failed",
"payment_id": "pi_abc123xyz",
"checkout_session_id": "cs_abc123xyz",
"merchant_id": "your_merchant_id",
"reference_id": "order_12345",
"status": "failed",
"amount": 150.00,
"currency": "BRL",
"method_code": "s-interio-mt-2",
"payment_method": "card",
"provider": "izipay",
"error_code": "CARD_DECLINED",
"error_message": "Card was declined by issuer",
"timestamp": "2025-01-04T12:30:01Z"
}Verifying Webhooks
All webhooks include an X-Signature header with an HMAC-SHA256 signature that you can use to verify authenticity:
Headers
| Header | Description |
|---|---|
X-Signature | HMAC-SHA256 signature of the request body |
X-Signature-Algorithm | Always "HMAC-SHA256" |
Verification (Python)
1 import hmac 2 import hashlib 3 4 def verify_webhook(payload: bytes, webhook_secret: str, received_signature: str) -> bool: 5 """ 6 Verify webhook signature using HMAC-SHA256. 7 8 Args: 9 payload: Raw request body bytes 10 webhook_secret: Your webhook secret from dashboard 11 received_signature: X-Signature header value 12 """ 13 expected = hmac.new( 14 webhook_secret.encode('utf-8'), 15 payload, 16 hashlib.sha256 17 ).hexdigest() 18 return hmac.compare_digest(expected, received_signature) 19 20 # Usage in Flask/FastAPI: 21 @app.post("/webhooks/cashier") 22 async def handle_webhook(request: Request): 23 payload = await request.body() 24 signature = request.headers.get("X-Signature") 25 26 if not verify_webhook(payload, WEBHOOK_SECRET, signature): 27 raise HTTPException(status_code=401, detail="Invalid signature") 28 29 # Process webhook...
Verification (Node.js)
1 const crypto = require('crypto'); 2 3 function verifyWebhook(payload, webhookSecret, receivedSignature) { 4 const expected = crypto 5 .createHmac('sha256', webhookSecret) 6 .update(payload) 7 .digest('hex'); 8 return crypto.timingSafeEqual( 9 Buffer.from(expected), 10 Buffer.from(receivedSignature) 11 ); 12 } 13 14 // Usage in Express: 15 app.post('/webhooks/cashier', express.raw({type: '*/*'}), (req, res) => { 16 const signature = req.headers['x-signature']; 17 18 if (!verifyWebhook(req.body, WEBHOOK_SECRET, signature)) { 19 return res.status(401).send('Invalid signature'); 20 } 21 22 const payload = JSON.parse(req.body); 23 // Process webhook... 24 });
Tip
Get your webhook_secret from the Dashboard under Configuration → Webhooks.
Handling Webhooks
Best Practices
Example Handler (Node.js)
1 app.post('/webhooks/cashier', async (req, res) => { 2 const { event, payment_id, reference_id, status } = req.body; 3 4 // Acknowledge immediately 5 res.status(200).send('OK'); 6 7 // Process webhook 8 if (event === 'payment.success') { 9 await updateOrderStatus(reference_id, 'paid'); 10 await sendConfirmationEmail(reference_id); 11 } else if (event === 'payment.failed') { 12 await updateOrderStatus(reference_id, 'payment_failed'); 13 } 14 });
Example Handler (Python)
1 @app.post("/webhooks/cashier") 2 async def handle_webhook(payload: dict): 3 event = payload.get("event") 4 payment_id = payload.get("payment_id") 5 reference_id = payload.get("reference_id") 6 7 if event == "payment.success": 8 await update_order_status(reference_id, "paid") 9 await send_confirmation_email(reference_id) 10 elif event == "payment.failed": 11 await update_order_status(reference_id, "payment_failed") 12 13 return {"status": "received"}
Retry Policy
If your server doesn't respond with HTTP 200-299, we retry the webhook:
After 5 failed attempts, the webhook is marked as failed and no more retries are sent.
Testing Webhooks
Use tools like webhook.site or ngrok to test webhook integration during development.