# Payment Callback (Webhook)

Receive real-time notifications when payment status changes. Webhooks are the primary way to stay updated on order status and automatically fulfill customer orders.

## Overview

MugglePay sends webhook events to notify your application whenever an order status changes. This is the **ONLY backend mechanism** you need to track payment status and automatically fulfill orders.

## Use Cases

Webhooks enable you to:

* **Update customer records** when subscription payments succeed
* **Send confirmation emails** after successful payments
* **Log accounting entries** when transfers are completed
* **Update inventory** when orders are paid
* **Activate services** immediately upon payment confirmation

## Webhook Configuration

### Setting Up Your Endpoint

1. **Provide callback URL** in your [Create Order](https://docs.mugglepay.com/order/createorder) request
2. **Ensure endpoint is public** and accessible from the internet
3. **Handle POST requests** with JSON payloads
4. **Return HTTP 200** to acknowledge receipt

### Authentication

Webhooks are authenticated using the `merchant_token` field you provide in the Create Order request. This prevents fraudulent callback attempts.

## Request Details

### HTTP Method

* **Method**: POST
* **Content-Type**: application/json
* **Authentication**: Token-based (from Create Order)

### Response Requirement

Your webhook endpoint must return HTTP 200 with this JSON response:

```json
{
  "status": 200
}
```

## Webhook Payload Structure

| Field               | Type     | Description                                        |
| ------------------- | -------- | -------------------------------------------------- |
| `order_id`          | `string` | MugglePay's internal order identifier              |
| `merchant_order_id` | `string` | Your custom order ID from Create Order             |
| `status`            | `string` | Current payment status (NEW, PENDING, PAID, etc.)  |
| `price_amount`      | `string` | Original price set by merchant (e.g., "29.99")     |
| `price_currency`    | `string` | Currency for pricing (e.g., "USD")                 |
| `pay_amount`        | `string` | Actual amount paid by customer                     |
| `pay_currency`      | `string` | Currency used for payment                          |
| `created_at`        | `string` | ISO 8601 timestamp of order creation               |
| `created_at_t`      | `number` | Unix timestamp (epoch) of order creation           |
| `merchant_token`    | `string` | Your custom merchant\_token for webhook validation |
| `meta`              | `object` | Additional payment information (optional)          |

## Code Examples

### Node.js/Express Example

```javascript
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhooks/payment', async (req, res) => {
  try {
    const {
      order_id,
      merchant_order_id,
      status,
      price_amount,
      price_currency,
      merchant_token
    } = req.body;

    // Validate webhook token
    if (!validateWebhookToken(merchant_token)) {
      console.error('Invalid webhook merchant_token');
      return res.status(401).json({ error: 'Unauthorized' });
    }

    // Handle different status changes
    switch (status) {
      case 'PAID':
        await fulfillOrder(merchant_order_id);
        console.log(`Order ${merchant_order_id} paid successfully`);
        break;
      
      case 'EXPIRED':
        await handleExpiredOrder(merchant_order_id);
        console.log(`Order ${merchant_order_id} expired`);
        break;
      
      case 'CANCELED':
        await handleCanceledOrder(merchant_order_id);
        console.log(`Order ${merchant_order_id} canceled`);
        break;
      
      default:
        console.log(`Order ${merchant_order_id} status: ${status}`);
    }

    // Always return 200 to acknowledge receipt
    res.json({ status: 200 });
    
  } catch (error) {
    console.error('Webhook processing error:', error);
    // Still return 200 to prevent retries
    res.json({ status: 200 });
  }
});

function validateWebhookToken(merchant_token) {
  // Implement your token validation logic
  return merchant_token === process.env.WEBHOOK_TOKEN;
}

async function fulfillOrder(merchantOrderId) {
  // Implement order fulfillment logic
  // Update database, send confirmation email, etc.
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});
```

### Python/Flask Example

```python
from flask import Flask, request, jsonify
import os

app = Flask(__name__)

@app.route('/webhooks/payment', methods=['POST'])
def payment_webhook():
    try:
        data = request.get_json()
        
        # Extract webhook data
        order_id = data.get('order_id')
        merchant_order_id = data.get('merchant_order_id')
        status = data.get('status')
        token = data.get('token')
        
        # Validate webhook token
        if not validate_webhook_token(token):
            print(f"Invalid webhook token for order {merchant_order_id}")
            return jsonify({'error': 'Unauthorized'}), 401
        
        # Process status change
        if status == 'PAID':
            fulfill_order(merchant_order_id)
            print(f"Order {merchant_order_id} fulfilled successfully")
        elif status == 'EXPIRED':
            handle_expired_order(merchant_order_id)
            print(f"Order {merchant_order_id} expired")
        elif status == 'CANCELED':
            handle_canceled_order(merchant_order_id)
            print(f"Order {merchant_order_id} canceled")
        
        # Return success response
        return jsonify({'status': 200})
        
    except Exception as e:
        print(f"Webhook processing error: {e}")
        # Return 200 even on error to prevent retries
        return jsonify({'status': 200})

def validate_webhook_token(token):
    return token == os.environ.get('WEBHOOK_TOKEN')

def fulfill_order(merchant_order_id):
    # Implement order fulfillment logic
    pass

if __name__ == '__main__':
    app.run(debug=True, port=3000)
```

### PHP Example

```php
<?php
// webhook.php

// Get webhook data
$webhook_data = json_decode(file_get_contents('php://input'), true);

// Validate webhook token
$expected_token = $_ENV['WEBHOOK_TOKEN'];
$received_token = $webhook_data['token'] ?? '';

if ($received_token !== $expected_token) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

// Extract webhook fields
$order_id = $webhook_data['order_id'];
$merchant_order_id = $webhook_data['merchant_order_id'];
$status = $webhook_data['status'];
$price_amount = $webhook_data['price_amount'];
$price_currency = $webhook_data['price_currency'];

// Process status change
switch ($status) {
    case 'PAID':
        fulfillOrder($merchant_order_id);
        error_log("Order {$merchant_order_id} paid successfully");
        break;
    
    case 'EXPIRED':
        handleExpiredOrder($merchant_order_id);
        error_log("Order {$merchant_order_id} expired");
        break;
    
    case 'CANCELED':
        handleCanceledOrder($merchant_order_id);
        error_log("Order {$merchant_order_id} canceled");
        break;
    
    default:
        error_log("Order {$merchant_order_id} status: {$status}");
}

// Return success response
http_response_code(200);
echo json_encode(['status' => 200]);

function fulfillOrder($merchantOrderId) {
    // Implement order fulfillment logic
    // Update database, send confirmation email, etc.
}

function handleExpiredOrder($merchantOrderId) {
    // Handle expired order logic
}

function handleCanceledOrder($merchantOrderId) {
    // Handle canceled order logic
}
?>
```

## Sample Webhook Payload

```json
{
  "merchant_order_id": "order_12345",
  "order_id": "94be2b2a-2905-4857-b701-b04e57e84593",
  "status": "PAID",
  "price_amount": "29.99",
  "price_currency": "USD",
  "pay_amount": "29.99",
  "pay_currency": "USD",
  "created_at": "2024-01-15T10:30:00.000Z",
  "created_at_t": 1705312200000,
  "token": "your_custom_token_123",
  "meta": {
    "payment": "USDT_ARB",
    "transaction_hash": "0x1234...",
    "network": "Arbitrum"
  }
}
```

## Retry Mechanism

MugglePay automatically retries failed webhook deliveries using exponential backoff:

| Attempt   | Delay       | Total Time         |
| --------- | ----------- | ------------------ |
| 1st       | 1 minute    | 1 minute           |
| 2nd       | 2 minutes   | 3 minutes          |
| 3rd       | 4 minutes   | 7 minutes          |
| 4th       | 8 minutes   | 15 minutes         |
| 5th       | 16 minutes  | 31 minutes         |
| 6th       | 32 minutes  | 63 minutes         |
| 7th       | 64 minutes  | 2 hours 7 minutes  |
| 8th       | 128 minutes | 4 hours 15 minutes |
| 9th       | 256 minutes | 8 hours 31 minutes |
| Continues | Doubles     | Up to 3 days       |

**Important**: Always return HTTP 200 to prevent unnecessary retries.

## Security Best Practices

### ✅ Do's

* **Validate webhook tokens** to prevent fraud
* **Use HTTPS** for all webhook endpoints
* **Implement idempotency** to handle duplicate webhooks
* **Log all webhook events** for debugging
* **Return HTTP 200** even on processing errors

### ❌ Don'ts

* Don't ignore webhook validation
* Don't expose webhook endpoints without authentication
* Don't return error codes that trigger retries
* Don't process webhooks synchronously for long operations

## Testing Webhooks

### Local Development

1. Use tools like [ngrok](https://ngrok.com/) to expose local endpoints
2. Test with small amounts in sandbox mode
3. Verify webhook delivery and processing

### Manual Testing

* Use the [Merchant Portal](https://merchants.mugglepay.com/transactions/orders)
* Click "Trigger Payment Callback" button for testing
* Monitor webhook delivery in your logs

## Troubleshooting

### Common Issues

#### Webhook Not Receiving

* **Check endpoint accessibility**: Ensure your URL is publicly accessible
* **Verify callback\_url**: Confirm it's correctly set in Create Order
* **Check firewall settings**: Ensure incoming POST requests are allowed
* **Test manually**: Use Merchant Portal to trigger test callbacks

#### Webhook Processing Errors

* **Validate merchant token**: Ensure webhook merchant\_token matches your expected value
* **Check payload structure**: Verify all required fields are present
* **Handle errors gracefully**: Return 200 even when processing fails
* **Monitor logs**: Check for specific error messages

#### Duplicate Webhooks

* **Implement idempotency**: Process each webhook only once
* **Check order\_id**: Use unique identifiers to prevent duplicates
* **Database constraints**: Add unique constraints on order processing

## Related Documentation

* [Create Order](https://docs.mugglepay.com/order/createorder) - Set up webhook endpoints
* [Order Status](https://github.com/MugglePay/MugglePay/blob/master/API/order/basic/OrderStatus.md) - Understand status values
* [Authentication](https://github.com/MugglePay/MugglePay/blob/master/API/order/basic/Authentication.md) - Secure your webhooks
* [Getting Started](https://github.com/MugglePay/MugglePay/blob/master/API/order/faq/GetStarted.md) - Complete integration guide

## Next Steps

1. **Implement webhook endpoint** following the examples above
2. **Test webhook delivery** with small test orders
3. **Add order fulfillment logic** for PAID status
4. **Monitor webhook processing** in production
5. **Set up alerting** for webhook failures


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mugglepay.com/order/paymentcallback.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
