Webhooks
Webhooks allow your application to receive real-time notifications when payment events occur. Instead of polling for updates, monterrey.app pushes events to your server.
Webhook Events
| Event | Description |
|---|---|
payment.created | A new payment was created |
payment.detecting | Transaction detected, waiting for confirmations |
payment.confirmed | Payment confirmed on blockchain |
payment.expired | Payment window expired |
payment.failed | Payment failed (wrong amount, wrong token, etc.) |
Webhook Payload
{
"id": "evt_1234567890",
"event": "payment.confirmed",
"createdAt": "2024-01-15T10:35:00Z",
"data": {
"id": "pay_abc123",
"merchantRef": "order_12345",
"amount": "1000000",
"currency": "USDC",
"chain": "polygon",
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f...",
"status": "confirmed",
"txHash": "0x123abc...",
"confirmedAt": "2024-01-15T10:35:00Z",
"metadata": {
"customer_id": "cust_abc123"
}
}
}Verifying Webhooks
All webhook payloads are signed with your webhook secret. Always verify the signature before processing events.
Signature Header
The X-Monterrey-Signature header contains the HMAC-SHA256 signature of the request body.
Verification Example
import crypto from "crypto";
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
export async function handleWebhook(req, res) {
const signature = req.headers["x-monterrey-signature"];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: "Invalid signature" });
}
// Process the webhook...
res.status(200).json({ received: true });
}Handling Webhooks
export async function handleWebhook(req, res) {
const { event, data } = req.body;
switch (event) {
case "payment.confirmed":
// Payment successful - fulfill the order
await fulfillOrder(data.merchantRef, {
txHash: data.txHash,
amount: data.amount,
currency: data.currency,
});
break;
case "payment.expired":
// Payment window closed - cancel or notify user
await expireOrder(data.merchantRef);
break;
case "payment.failed":
// Something went wrong
await handleFailedPayment(data.merchantRef, data.failureReason);
break;
}
// Always return 200 to acknowledge receipt
res.status(200).json({ received: true });
}Retry Policy
If your endpoint returns a non-2xx status code or times out, we'll retry the webhook:
- Retry 1: After 1 minute
- Retry 2: After 5 minutes
- Retry 3: After 30 minutes
- Retry 4: After 2 hours
- Retry 5: After 24 hours
After 5 failed attempts, the webhook is marked as failed. You can view failed webhooks in the Developer Portal.
Best Practices
- Return quickly: Respond with 200 within 30 seconds. Process events asynchronously if needed.
- Handle duplicates: Use the event
idto deduplicate. Webhooks may be delivered more than once. - Verify signatures: Always validate the webhook signature before processing.
- Use HTTPS: Only HTTPS endpoints are supported for webhooks.
- Log everything: Keep logs of received webhooks for debugging.
Managing Webhooks via API
Create Webhook Endpoint
curl -X POST https://api.monterrey.app/v1/webhooks \
-H "X-API-Key: mk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yoursite.com/webhooks/monterrey",
"events": ["payment.confirmed", "payment.expired"],
"secret": "whsec_your_secret_here"
}'List Webhook Endpoints
curl https://api.monterrey.app/v1/webhooks \
-H "X-API-Key: mk_live_your_api_key"Delete Webhook Endpoint
curl -X DELETE https://api.monterrey.app/v1/webhooks/wh_123 \
-H "X-API-Key: mk_live_your_api_key"