Events

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:

webhook-payload.json
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.success

Payment completed successfully

payment.failed

Payment failed or was declined

payment.pending

Payment is pending (e.g., awaiting PIX confirmation)

payment.processing

Payment is being processed

payment.cancelled

Payment was cancelled by customer

Payload Fields

FieldTypeDescription
eventstringEvent type (see above)
payment_idstringOur payment identifier (pi_xxx)
checkout_session_idstringCheckout session identifier (cs_xxx)
merchant_idstringYour merchant identifier
reference_idstringYour order/transaction ID
statusstringPayment status
amountnumberTransaction amount
currencystringCurrency code
method_codestringPayment method used
paid_atstringISO 8601 timestamp when paid (success only)
error_messagestringError description (failed only)
payment_methodstringPayment method type (pix, card, boleto, etc.)
providerstringPayment provider used (sfp, izipay, tupay, etc.)
metadataobjectYour custom metadata from checkout session
timestampstringISO 8601 timestamp of the webhook

Example: Success Webhook

success.json
{
  "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

failed.json
{
  "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

HeaderDescription
X-SignatureHMAC-SHA256 signature of the request body
X-Signature-AlgorithmAlways "HMAC-SHA256"

Verification (Python)

verify_webhook.py
1import hmac
2import hashlib
3
4def 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")
22async 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)

verify_webhook.js
1const crypto = require('crypto');
2
3function 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:
15app.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

1
Respond quickly - Return HTTP 200 within 30 seconds
2
Process asynchronously - Queue webhooks for later processing if needed
3
Handle duplicates - Use payment_id to deduplicate
4
Verify signature - Check X-Signature header to confirm authenticity

Example Handler (Node.js)

handler.js
1app.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)

handler.py
1@app.post("/webhooks/cashier")
2async 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:

Retry Schedule
1st
1 min
2nd
5 min
3rd
15 min
4th
1 hour
5th
4 hours

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.