kamiPay LogokamiPay Docs

Webhook Simulator

Test your webhook integration without real payments

This is a simulator for our webhook service that allows our users in the development environment to emulate all different conditions any pix can take, for both pay-ins and pay-outs.

This is specifically useful also to adjust the authentication process, as simulated webhooks are signed with your signature_key, which comes with your credentials, exactly as real ones are. See Authentication.

Payments in the development environment (sandbox) are real. Sandbox QR codes are real Pix, paid from real bank accounts, and each charge's USDt settles to your wallet on Polygon mainnet. That is why this simulator exists: use it to test your webhook handling without moving real money.

How it works?

Before you start:

  • Your notif_endpoint must be configured with integrations support. The simulator delivers each webhook there; without one, the request is rejected with 400.
  • Set pix_id to one of your own operation_ids (ptxr_…), as returned when you created the charge, so your handler can match the webhook to its charge.

Pick any webhook example from the documentation, you can edit the fields and post it to this endpoint. kamiPay delivers it to your notif_endpoint, signed with your signature_key as a real webhook is.

For instance, if you are testing pay-ins, you will generate a QR Code and then you can self-send the different status updates, like processing, done, failed without needing to make a real payment to that QR code.

This endpoint allows you to test all webhook handling flows without waiting for actual payment processing or making real transactions.

Test webhook

const url = `${baseURL}/v1/emulator/push_webhook`;

// Use any webhook JSON example from the documentation
const body = {
  "pix_id": "ptxr_01kr3m9p7n2s4d8h6e5b3t7w1k",
  "status": "done",
  "tx_id": "0xa3f9b2c1e7d4865094bd28fa1c3e6b85907df42a3b9c1de80f5a672bc41e9d3f",
  "timestamp": "2026-04-27 15:51:40.171821-03:00",
  "type": "charge",
  "qr_type": "dynamic",
  "kamipay_id": "dqr_01kr3m9q5h7w2v4n6b8s3d5e9p",
  "data": {
    "bank_txid": "E29384756202604271548aB7cD3xY9mZ",
    "bank_account_nr": "29384756-0001-0001349872",
    "internal_pix_id": "8a3f1c9d72b46e08fd5a912ec47b3f06",
    "amount_brl": "5.28",
    "amount_usdt": "1.033355",
    "address_out": "0x7a3F9b2C1e8D5462bA9c7F3e6D85907df41A2c3B",
    "name": "Satoshi Nakamoto"
  }
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});
import requests
import json

url = f"{base_url}/v1/emulator/push_webhook"

# Use any webhook JSON example from the documentation
body = {
  "pix_id": "ptxr_01kr3m9p7n2s4d8h6e5b3t7w1k",
  "status": "done",
  "tx_id": "0xa3f9b2c1e7d4865094bd28fa1c3e6b85907df42a3b9c1de80f5a672bc41e9d3f",
  "timestamp": "2026-04-27 15:51:40.171821-03:00",
  "type": "charge",
  "qr_type": "dynamic",
  "kamipay_id": "dqr_01kr3m9q5h7w2v4n6b8s3d5e9p",
  "data": {
    "bank_txid": "E29384756202604271548aB7cD3xY9mZ",
    "bank_account_nr": "29384756-0001-0001349872",
    "internal_pix_id": "8a3f1c9d72b46e08fd5a912ec47b3f06",
    "amount_brl": "5.28",
    "amount_usdt": "1.033355",
    "address_out": "0x7a3F9b2C1e8D5462bA9c7F3e6D85907df41A2c3B",
    "name": "Satoshi Nakamoto"
  }
}

headers = {
    'Authorization': f'Bearer {access_token}',
    'Content-Type': 'application/json',
}

response = requests.post(url, json=body, headers=headers)
package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    url := fmt.Sprintf("%s/v1/emulator/push_webhook", baseURL)
    
    // Use any webhook JSON example from the documentation, sent as raw JSON.
    // Don't build it from a map: json.Marshal sorts a map's keys, so the
    // simulated webhook would not arrive in the order a real one does.
    requestBody := []byte(`{
        "pix_id": "ptxr_01kr3m9p7n2s4d8h6e5b3t7w1k",
        "status": "done",
        "tx_id": "0xa3f9b2c1e7d4865094bd28fa1c3e6b85907df42a3b9c1de80f5a672bc41e9d3f",
        "timestamp": "2026-04-27 15:51:40.171821-03:00",
        "type": "charge",
        "qr_type": "dynamic",
        "kamipay_id": "dqr_01kr3m9q5h7w2v4n6b8s3d5e9p",
        "data": {
            "bank_txid": "E29384756202604271548aB7cD3xY9mZ",
            "bank_account_nr": "29384756-0001-0001349872",
            "internal_pix_id": "8a3f1c9d72b46e08fd5a912ec47b3f06",
            "amount_brl": "5.28",
            "amount_usdt": "1.033355",
            "address_out": "0x7a3F9b2C1e8D5462bA9c7F3e6D85907df41A2c3B",
            "name": "Satoshi Nakamoto"
        }
    }`)
    
    // Create request
    req, _ := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
    req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", access_token))
    req.Header.Add("Content-Type", "application/json")
    
    // Make the request
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error making request:", err)
        return
    }
    defer resp.Body.Close()
    
    // Read the response body
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println(string(body))
}

Response

A 200 from the simulator means that the webhook reached your endpoint, not that your handler accepted the webhook. response is the HTTP status that your endpoint answered, an error included:

{
  "response": 200,
  "payload": {
    "pix_id": "ptxr_01kr3m9p7n2s4d8h6e5b3t7w1k",
    "status": "done",
    "tx_id": "0xa3f9b2c1e7d4865094bd28fa1c3e6b85907df42a3b9c1de80f5a672bc41e9d3f",
    "timestamp": "2026-04-27 15:51:40.171821-03:00",
    "type": "charge",
    "qr_type": "dynamic",
    "kamipay_id": "dqr_01kr3m9q5h7w2v4n6b8s3d5e9p",
    "data": {
      "bank_txid": "E29384756202604271548aB7cD3xY9mZ",
      "bank_account_nr": "29384756-0001-0001349872",
      "internal_pix_id": "8a3f1c9d72b46e08fd5a912ec47b3f06",
      "amount_brl": "5.28",
      "amount_usdt": "1.033355",
      "address_out": "0x7a3F9b2C1e8D5462bA9c7F3e6D85907df41A2c3B",
      "name": "Satoshi Nakamoto"
    }
  }
}

Your notif_endpoint is not configured, so the simulator sent nothing:

{
  "detail": "No notif_endpoint configured: ask integrations support to set one"
}

With an invalid or expired token:

{
  "detail": "Could not validate credentials"
}

The simulator received no answer from your notif_endpoint, for example because of an invalid URL, a refused connection, or a timeout:

{
  "detail": "Could not reach your notif_endpoint"
}

The webhook simulator is only available in the development environment and should be used for testing purposes only.

You can use the webhook example payloads from the Pay-In and Pay-Out webhook documentation sections to test different scenarios.

On this page