> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nomadpay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

Webhooks are used by Nomad Pay to send real-time notifications to your server about events, such as a successful payment.

## Overview

This is an **outbound** request from Nomad Pay **to your server**.

When an event occurs (like `payment.succeeded`), Nomad Pay will send a `POST` request to the Webhook URL you configured in your merchant dashboard.

* **Method:** `POST`
* **Content-Type:** `application/json`
* **Signature Algorithm:** `Ed25519`

## Verifying Webhooks

To ensure the request is truly from Nomad Pay and has not been tampered with, you **must** verify the signature.

This signature is **different** from the one you send. We use **our own Ed25519 Private Key** to sign the callback. You must use **our Ed25519 Public Key** to verify it.

* **Nomad Pay Public Key:**
  * You can find this key in your merchant dashboard's Developer section.
  * Example: `your_nomad_pay_public_key_hex_string`
* **Signature Header:**
  * The signature will be in the `x-signature` header.

## Callback Body

The body contains the details of the event.

| Field           | Type     | Description                                                  |
| :-------------- | :------- | :----------------------------------------------------------- |
| `order_id`      | `string` | The Nomad Pay Payment ID (e.g., `pay_123456789`).            |
| `secret_id`     | `string` | Your original order ID from your system.                     |
| `blockchain`    | `string` | The name of the blockchain (e.g., `Ethereum`).               |
| `transaction`   | `string` | The on-chain transaction hash (Tx Hash).                     |
| `sender`        | `string` | The customer's wallet address.                               |
| `receiver`      | `string` | The merchant's receiving address.                            |
| `token`         | `string` | The token used for payment (e.g., `USDT`).                   |
| `order_amount`  | `string` | The original amount specified in the order (e.g., `100.50`). |
| `amount`        | `string` | The actual on-chain amount received (e.g., `100.50`).        |
| `payload`       | `object` | The `meta_data` object you sent in the original request.     |
| `status`        | `string` | The status of the transaction (e.g., `success`, `failed`).   |
| `confirmations` | `int`    | The number of block confirmations.                           |
| `created_at`    | `string` | ISO8601 timestamp of creation.                               |
| `confirmed_at`  | `string` | ISO8601 timestamp of confirmation.                           |

**Example Callback Body:**

```
{
  "order_id": "pay_123456789",
  "secret_id": "your-order-id-123",
  "blockchain": "Ethereum",
  "transaction": "0xabc123...",
  "sender": "0xuser...",
  "receiver": "0xmerchant...",
  "token": "USDT",
  "order_amount": "100.50",
  "amount": "100.50",
  "payload": {
    "order_id": "20231101123456",
    "description": "VIP Subscription"
  },
  "after_block": 12345678,
  "commitment": "",
  "confirmations": 12,
  "created_at": "2024-06-01T12:34:56Z",
  "confirmed_at": "2024-06-01T12:35:10Z",
  "status": "success"
}
```

## Verifying the Signature

You must verify the `x-signature` against the **raw JSON callback body** using the **Nomad Pay Public Key**.

**Example (Go):**

```
import (
	"crypto/ed25519"
	"encoding/hex"
)

// Verify checks an Ed25519 signature.
// publicKeyHex is the Nomad Pay Public Key from your dashboard.
// message is the raw JSON body of the callback.
// signatureHex is the string from the x-signature header.
func Verify(publicKeyHex string, message []byte, signatureHex string) (bool, error) {
	publicKeyBytes, err := hex.DecodeString(publicKeyHex)
	if err != nil {
		return false, err
	}
	publicKey := ed25519.PublicKey(publicKeyBytes)

	signatureBytes, err := hex.DecodeString(signatureHex)
	if err != nil {
		return false, err
	}

	return ed25519.Verify(publicKey, message, signatureBytes), nil
}

// In your webhook handler:
// 1. Get the raw request body (message)
// 2. Get the x-signature header (signatureHex)
// 3. Get your Nomad Pay Public Key (publicKeyHex)
// 4. isValid, _ := Verify(publicKeyHex, message, signatureHex)
// 5. IF isValid is true, process the order.
// 6. Respond with HTTP 200 and the string "success".
```

## Full Integration Example

Here is a complete example using the **Gin** web framework (Go) to handle the webhook callback, verify the signature, and parse the payload.

```
package main

import (
	"crypto/ed25519"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

// Payload defines the structure of the 'payload' field in the request
type Payload struct {
	Name     string                 `json:"name"`
	Email    string                 `json:"email"`
	Address  string                 `json:"address"`
	Note     string                 `json:"note"`
	MetaData map[string]interface{} `json:"meta_data"`
}

// PayRequest defines the structure of the webhook request body
type PayRequest struct {
	OrderID     string  `json:"order_id" binding:"required"`
	SecretID    string  `json:"secret_id" binding:"required"`
	Blockchain  string  `json:"blockchain" binding:"required"`
	Transaction string  `json:"transaction" binding:"required"`
	Sender      string  `json:"sender" binding:"required"`
	Receiver    string  `json:"receiver" binding:"required"`
	Token       string  `json:"token" binding:"required"`
	OrderAmount string  `json:"order_amount" binding:"required"`
	Amount      string  `json:"amount" binding:"required"`
	AfterBlock  int     `json:"after_block" binding:"omitempty"`
	Status      string  `json:"status,omitempty"`
	Payload     Payload `json:"payload,omitempty" binding:"omitempty"`
}

// PayResponse defines the structure of your response
type PayResponse struct {
	Success    bool        `json:"success"`
	Message    string      `json:"message"`
	Data       PayRequest  `json:"data"`
	ReceivedAt string      `json:"received_at"`
}

// handleCallBack handles the POST request from Nomad Pay
func handleCallBack(c *gin.Context) {
	// 1. Get raw request body
	requestBytes, err := c.GetRawData()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"message": "params err: " + err.Error(),
		})
		return
	}

	// 2. Get the signature from header
	signature := c.GetHeader("x-signature")

	// 3. Verify the signature
	// TODO: Replace with your actual Nomad Pay Public Key
	nomadPayPublicKey := "ba2c5f01d09be43b1af16d5ed2c349d29b232ad0b97c56b24efdcca200b44894" 
	
	valid, err := Verify(nomadPayPublicKey, requestBytes, signature)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"message": "signature check error: " + err.Error(),
		})
		return
	}

	if !valid {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"message": "signature is not valid",
		})
		return
	}

	// 4. Bind JSON to struct
	var req PayRequest
	if err := json.Unmarshal(requestBytes, &req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"success": false,
			"message": "json unmarshal err: " + err.Error(),
		})
		return
	}

	// 5. Process business logic
	fmt.Printf("Success received data: orderID=%s, blockchain=%s, status=%s\n",
		req.OrderID, req.Blockchain, req.Status)

	// 6. Return success response
	resp := PayResponse{
		Success:    true,
		Message:    "Data received and verified successfully",
		Data:       req,
		ReceivedAt: time.Now().Format(time.RFC3339),
	}
	c.JSON(http.StatusOK, resp)
}

func main() {
	r := gin.Default()
	
	// Setup route
	api := r.Group("/api")
	{
		api.POST("/payCallBack", handleCallBack)
	}

	port := "8080"
	fmt.Printf("Server starting: http://localhost:%s\n", port)
	
	// Start server
	if err := r.Run(":" + port); err != nil {
		fmt.Printf("Server start failed: %v\n", err)
		return
	}
}

// Verify checks the Ed25519 signature
func Verify(publicKeyHex string, message []byte, signatureHex string) (bool, error) {
	// Decode Public Key
	publicKeyBytes, err := hex.DecodeString(publicKeyHex)
	if err != nil {
		return false, fmt.Errorf("failed to decode public key: %v", err)
	}
	publicKey := ed25519.PublicKey(publicKeyBytes)

	// Decode Signature
	signatureBytes, err := hex.DecodeString(signatureHex)
	if err != nil {
		return false, fmt.Errorf("failed to decode signature: %v", err)
	}

	// Verify
	return ed25519.Verify(publicKey, message, signatureBytes), nil
}
```

```
package main

import (
	"crypto/ed25519"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"

	"[github.com/gin-gonic/gin](https://github.com/gin-gonic/gin)"
)

// ... (Payload structs remain the same) ...

func handleCallBack(c *gin.Context) {
	// 1. Get raw request body
	requestBytes, err := c.GetRawData()
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"message": "params err"})
		return
	}

	// 2. Get Signature AND Public Key from headers
	signature := c.GetHeader("x-signature")
	publicKeyHex := c.GetHeader("x-api-key") // <--- DYNAMIC KEY RETRIEVAL

	if publicKeyHex == "" {
		c.JSON(http.StatusBadRequest, gin.H{"message": "missing x-api-key header"})
		return
	}

	// 3. Verify the signature using the key from the header
	valid, err := Verify(publicKeyHex, requestBytes, signature)
	if err != nil || !valid {
		c.JSON(http.StatusUnauthorized, gin.H{"message": "invalid signature"})
		return
	}

	// 4. Process the webhook...
	c.JSON(http.StatusOK, gin.H{"message": "success"})
}

// Verify checks the Ed25519 signature
func Verify(publicKeyHex string, message []byte, signatureHex string) (bool, error) {
	publicKeyBytes, err := hex.DecodeString(publicKeyHex)
	if err != nil {
		return false, err
	}
	publicKey := ed25519.PublicKey(publicKeyBytes)

	signatureBytes, err := hex.DecodeString(signatureHex)
	if err != nil {
		return false, err
	}

	return ed25519.Verify(publicKey, message, signatureBytes), nil
}
```

## Important Notes

* **Verify Signatures:** Always verify the signature. This is critical for security.
* **Respond Quickly:** Your endpoint must respond with an `HTTP 200` status and the raw string "success" within 5 seconds, or we will consider the callback failed and retry.
* **Idempotency:** Because we retry failed webhooks, your system must be able to handle receiving the same event multiple times. Use the `order_id` or `secret_id` to de-duplicate.
