Webhooks
Webhooks allow you to receive real-time notifications about payment events. Instead of polling the API, Yugo sends HTTP POST requests to your server when events occur.
Overview
When you create a transaction with a webhookUrl, Yugo automatically sends status updates to that endpoint.
Setting Up Webhooks
1. Create a Webhook Endpoint
Your endpoint must:
- Accept HTTP POST requests
- Parse JSON request bodies
- Return HTTP 200 status code
- Respond within 30 seconds
2. Include Webhook URL in Requests
When creating a transaction, include your webhook URL:
{
"amount": "100.00",
"names": "John Doe",
"userId": "user-123",
"webhookUrl": "https://yourdomain.com/webhooks/yugo",
"webhookPayload": "order-12345"
}Webhook Events
A2A Payments
Webhook payload for A2A payment status updates:
{
"status": "AUTHORIZED",
"webhookPayload": "your-transaction-id",
"payerBankAccount": {
"iban": "DE89370400440532013000",
"holderNames": "John Doe",
"bank": "Revolut"
}
}| Field | Description |
|---|---|
status | Payment status (INTENT, STARTED, AUTHORIZED, FAILED_INTENT) |
webhookPayload | The string you provided when creating the transaction |
payerBankAccount | Bank details (included when payment is authorized) |
Merchant On-ramp
Webhook payload for merchant on-ramp events:
{
"eventType": "merchant_on_ramp_success",
"transactionId": "5d141fb0-dea0-4499-8361-0915807d1151",
"amount": 100.0,
"currency": "EUR",
"timestamp": "2025-04-17T10:14:22.001Z",
"webhookPayload": "internal-order-id-xyz"
}| Field | Description |
|---|---|
eventType | Type of event (merchant_on_ramp_success) |
transactionId | Yugo transaction ID |
amount | Transaction amount |
currency | Transaction currency |
timestamp | Event timestamp (ISO 8601) |
webhookPayload | The string you provided when creating the transaction |
Implementing Your Webhook Handler
Node.js Example
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhooks/yugo', async (req, res) => {
const { status, webhookPayload, eventType, transactionId } = req.body;
// A2A Payment webhook
if (status) {
console.log(`Payment ${webhookPayload} status: ${status}`);
if (status === 'AUTHORIZED') {
// Handle successful authorization
await processAuthorizedPayment(webhookPayload);
} else if (status === 'FAILED_INTENT') {
// Handle failed payment
await handleFailedPayment(webhookPayload);
}
}
// Merchant On-ramp webhook
if (eventType === 'merchant_on_ramp_success') {
console.log(`On-ramp success for transaction: ${transactionId}`);
// Verify the transaction status
const transaction = await verifyTransaction(transactionId);
if (transaction.bankDepositStatus === 'SUCCESS') {
await processSuccessfulDeposit(webhookPayload);
}
}
// Always return 200 to acknowledge receipt
res.status(200).json({
message: 'Webhook processed successfully'
});
});
app.listen(3000);Python Example
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhooks/yugo', methods=['POST'])
def handle_webhook():
data = request.json
# A2A Payment webhook
if 'status' in data:
status = data['status']
webhook_payload = data.get('webhookPayload')
if status == 'AUTHORIZED':
process_authorized_payment(webhook_payload)
elif status == 'FAILED_INTENT':
handle_failed_payment(webhook_payload)
# Merchant On-ramp webhook
if data.get('eventType') == 'merchant_on_ramp_success':
transaction_id = data['transactionId']
# Verify and process
verify_and_process(transaction_id)
return jsonify({'message': 'Webhook processed successfully'}), 200
if __name__ == '__main__':
app.run(port=3000)Best Practices
1. Verify Transactions
Always verify webhook data by calling the Get Transaction endpoint:
curl https://api.yugo.finance/transaction/?transactionId={transactionId} \
-H "x-api-key: YOUR_API_KEY"2. Handle Idempotency
Webhooks may be sent multiple times. Ensure your handler is idempotent:
app.post('/webhooks/yugo', async (req, res) => {
const { transactionId } = req.body;
// Check if already processed
const existing = await db.findTransaction(transactionId);
if (existing && existing.processed) {
return res.status(200).json({ message: 'Already processed' });
}
// Process and mark as handled
await processWebhook(req.body);
await db.markAsProcessed(transactionId);
res.status(200).json({ message: 'Processed' });
});3. Respond Quickly
Return HTTP 200 immediately, then process asynchronously:
app.post('/webhooks/yugo', (req, res) => {
// Acknowledge immediately
res.status(200).json({ message: 'Received' });
// Process in background
processWebhookAsync(req.body).catch(console.error);
});4. Log Everything
Keep detailed logs for debugging:
app.post('/webhooks/yugo', (req, res) => {
console.log('Webhook received:', JSON.stringify(req.body));
// ... process
});Retry Policy
If your endpoint doesn't return HTTP 200:
| Integration | Retry Policy |
|---|---|
| Merchant On-ramp | Up to 5 retries with exponential backoff |
| A2A Payments | Retries on failure |
Testing Webhooks
Local Development
Use tools like ngrok to expose your local server:
ngrok http 3000Then use the ngrok URL as your webhookUrl:
{
"webhookUrl": "https://abc123.ngrok.io/webhooks/yugo"
}Webhook Debugging
- Log all incoming webhook payloads
- Verify the transaction via API after receiving webhook
- Check your server logs for errors
- Ensure your endpoint returns HTTP 200
Security Considerations
- Use HTTPS for your webhook endpoint
- Validate the webhook payload by verifying the transaction
- Don't trust webhook data alone - always verify via API
- Consider implementing webhook signature verification (contact Yugo for details)