Notifications
Query and replay webhook notifications
This endpoint allows you to search your webhook notification history and re-trigger failed or missed webhook deliveries. Useful for debugging integration issues or recovering from temporary webhook endpoint outages.
All notification endpoints are rate-limited to 5 requests per minute per IP address.
List Notifications
GET /v2/notifications
Search and list notifications of one type, with optional filters.
Deliveries from the Webhook Simulator aren't recorded here: only real notifications are.
Query Parameters
| Name | Type | Description |
|---|---|---|
| e2e_id | string | Bank e2e_id (E* for payments, D* for refunds). Min 20 characters. |
| transaction_id | string | KamiPay transaction ID (kamipay_id or kamipay_refund_id). For a charge, use its kamipay_id (dqr_…, or cqr_… for a printed sale) or its operation_id (ptxr_…). |
| status | string | Filter by webhook status. The values depend on type: done, processing, failed or refunded for pay; done or processing for charge; refunded for refund. Any other combination is rejected with 422. |
| type | string | Required. pay (payout), charge (pay-in), or refund. Without it, the request is rejected with 422. |
| from_date | datetime | Start date filter (ISO 8601 format). Filters on the notification's last_updated_at, which every delivery, retry and replay renews. |
| to_date | datetime | End date filter (ISO 8601 format). Filters on the notification's last_updated_at, which every delivery, retry and replay renews. |
| limit | int | Maximum results to return (1-100). Default: 25. |
| offset | int | Pagination offset. Default: 0. |
Filtering by a charge's id returns one notification: the one delivered or replayed last. Once the charge is paid, that's normally its done; if a replay or a retry redelivers its processing afterwards, you get the processing. To see both, list by type=charge and dates, without transaction_id.
Example Request
const params = new URLSearchParams({
type: 'pay',
status: 'failed',
from_date: '2026-01-01T00:00:00Z',
to_date: '2026-01-28T23:59:59Z',
limit: '50'
});
const url = `${baseURL}/v2/notifications?${params}`;
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
});import requests
params = {
'type': 'pay',
'status': 'failed',
'from_date': '2026-01-01T00:00:00Z',
'to_date': '2026-01-28T23:59:59Z',
'limit': 50
}
url = f"{base_url}/v2/notifications"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
}
response = requests.get(url, params=params, headers=headers)package main
import (
"fmt"
"net/http"
"net/url"
)
func main() {
baseURL := "https://api.kamipay.io"
params := url.Values{}
params.Add("type", "pay")
params.Add("status", "failed")
params.Add("from_date", "2026-01-01T00:00:00Z")
params.Add("to_date", "2026-01-28T23:59:59Z")
params.Add("limit", "50")
url := fmt.Sprintf("%s/v2/notifications?%s", baseURL, params.Encode())
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", access_token))
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
}Response
A pay-in notification's payload is the charge's Pay-In webhook, as kamiPay sent it: parse it with the code that parses your webhooks.
{
"notifications": [
{
"notification_id": "a1b2c3d4-0001-4000-8000-000000000001",
"last_updated_at": "2026-01-22T12:30:00+00:00",
"payload": {
"status": "done",
"transaction_hash": null,
"data": {
"operation_id": "txc_01kfktest001done",
"address_in": "0x14eD551D293E41A26E62D605Bc64F9beC154A698",
"usdt_amount": "19.10718",
"brl_amount": "100.00",
"bank_txid": "E27084098202601221953g4eyYfCurMV",
"pix_key": "11999887766",
"name": "Usuario Test Uno"
},
"type": "pay",
"timestamp": "2026-01-22 09:30:00.000000+0000",
"external_id": "ext_payout_done_001",
"kamipay_id": "txc_01kfktest001done",
"service_type": "prepaid"
}
}
],
"total": 1,
"limit": 50,
"offset": 0
}{
"notifications": [
{
"notification_id": "d1b2c3d4-0001-4000-8000-000000000001",
"last_updated_at": "2026-04-27T18:51:40.487215+00:00",
"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"
}
}
}
],
"total": 1,
"limit": 50,
"offset": 0
}{
"detail": "Invalid e2e_id format: ABC123. Expected format: E* for payments, D* for refunds (min 20 characters)"
}With an invalid or expired token:
{
"detail": "Could not validate credentials"
}Get Notification by Transaction ID
GET /v2/notifications/{kp_transaction_id}
Retrieve a single notification by its KamiPay transaction ID.
For a charge, you get one notification: the one delivered or replayed last. Once the charge is paid, that's normally its done; if a replay or a retry redelivers its processing afterwards, you get the processing. To see both, list by type=charge and dates.
Path Parameters
| Name | Type | Description |
|---|---|---|
| kp_transaction_id | string | Required. The KamiPay transaction ID. For a charge, use its kamipay_id (dqr_…, or cqr_… for a printed sale) or its operation_id (ptxr_…). |
Query Parameters
| Name | Type | Description |
|---|---|---|
| type | string | Transaction type hint: pay, charge, or refund. Auto-detected from ID prefix if not provided. |
Auto-detection: The type is automatically detected from the transaction ID prefix:
txc_*: payout (pay)dqr_*orcqr_*(a charge'skamipay_id,cqr_for a printed sale) andptxr_*(itsoperation_id): pay-in (charge)rpt_*: refund
Example Request
const kp_transaction_id = "txc_01jrazd7a9fqpp0bym8km1dbd6";
const url = `${baseURL}/v2/notifications/${kp_transaction_id}`;
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
});import requests
kp_transaction_id = "txc_01jrazd7a9fqpp0bym8km1dbd6"
url = f"{base_url}/v2/notifications/{kp_transaction_id}"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
}
response = requests.get(url, headers=headers)package main
import (
"fmt"
"net/http"
)
func main() {
kpTransactionID := "txc_01jrazd7a9fqpp0bym8km1dbd6"
url := fmt.Sprintf("%s/v2/notifications/%s", baseURL, kpTransactionID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", access_token))
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
}Response
{
"notification_id": "a1b2c3d4-0001-4000-8000-000000000001",
"last_updated_at": "2026-01-22T12:30:00+00:00",
"payload": {
"status": "done",
"transaction_hash": null,
"data": {
"operation_id": "txc_01kfktest001done",
"address_in": "0x14eD551D293E41A26E62D605Bc64F9beC154A698",
"usdt_amount": "19.10718",
"brl_amount": "100.00",
"bank_txid": "E27084098202601221953g4eyYfCurMV",
"pix_key": "11999887766",
"name": "Usuario Test Uno"
},
"type": "pay",
"timestamp": "2026-01-22 09:30:00.000000+0000",
"external_id": "ext_payout_done_001",
"kamipay_id": "txc_01kfktest001done",
"service_type": "prepaid"
}
}{
"notification_id": "d1b2c3d4-0001-4000-8000-000000000001",
"last_updated_at": "2026-04-27T18:51:40.487215+00:00",
"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"
}
}
}{
"detail": "Invalid transaction ID format: invalid_id. Expected prefixes: txc_, txp_, dqr_, lnk_, sqr_, cqr_, ptxr_, pir_, rpt_, rbc_"
}{
"detail": "Notification not found"
}Get Refunds for Transaction
GET /v2/notifications/{kp_transaction_id}/refunds
Find all refund notifications associated with a transaction.
Path Parameters
| Name | Type | Description |
|---|---|---|
| kp_transaction_id | string | Required. The original KamiPay transaction ID. |
Example Request
const kp_transaction_id = "txc_01jrazd7a9fqpp0bym8km1dbd6";
const url = `${baseURL}/v2/notifications/${kp_transaction_id}/refunds`;
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
});import requests
kp_transaction_id = "txc_01jrazd7a9fqpp0bym8km1dbd6"
url = f"{base_url}/v2/notifications/{kp_transaction_id}/refunds"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
}
response = requests.get(url, headers=headers)package main
import (
"fmt"
"net/http"
)
func main() {
kpTransactionID := "txc_01jrazd7a9fqpp0bym8km1dbd6"
url := fmt.Sprintf("%s/v2/notifications/%s/refunds", baseURL, kpTransactionID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", access_token))
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
}Response
{
"original_transaction_id": "txc_01kfktest004refunded",
"refunds": [
{
"notification_id": "c1b2c3d4-0001-4000-8000-000000000001",
"last_updated_at": "2026-01-22T13:00:00+00:00",
"payload": {
"kamipay_refund_id": "rpt_01kfktest001refund",
"kamipay_id": "txc_01kfktest004refunded",
"external_id": "ext_payout_refunded",
"refund_bank_txid": "D14796606202601222051497837G0003",
"original_bank_txid": "E270840982026012219462noRErpDQMV",
"service_type": "prepaid",
"type": "pay",
"status": "refunded",
"refunded_brl_amount": "171.93",
"refunded_usdt_amount": "32.850973",
"original_brl_amount": "171.93",
"original_usdt_amount": "32.850973",
"original_recipient_name": "Usuario Refund Test",
"timestamp": "2026-01-22 10:00:00.000000+0000"
}
}
],
"total": 1
}{
"detail": "No refunds found for this transaction"
}Replay Single Notification
POST /v2/notifications/{kp_transaction_id}/replay
Queue a single notification for replay. The notification will be re-delivered to your webhook URL.
A replay arrives 5 to 10 minutes later. queued means the notification was scheduled for redelivery, not sent: a retry job, which runs every 5 minutes, delivers it 5 to 10 minutes after your request. Replaying it again in the meantime answers already_queued.
Path Parameters
| Name | Type | Description |
|---|---|---|
| kp_transaction_id | string | Required. The KamiPay transaction ID to replay. For a charge, use its kamipay_id (dqr_…, or cqr_… for a printed sale) or its operation_id (ptxr_…). |
The notification type is auto-detected from the transaction ID prefix. Ensure you're using the correct transaction ID format.
Example Request
const kp_transaction_id = "txc_01jrazd7a9fqpp0bym8km1dbd6";
const url = `${baseURL}/v2/notifications/${kp_transaction_id}/replay`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
});import requests
kp_transaction_id = "txc_01jrazd7a9fqpp0bym8km1dbd6"
url = f"{base_url}/v2/notifications/{kp_transaction_id}/replay"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
}
response = requests.post(url, headers=headers)package main
import (
"fmt"
"net/http"
)
func main() {
kpTransactionID := "txc_01jrazd7a9fqpp0bym8km1dbd6"
url := fmt.Sprintf("%s/v2/notifications/%s/replay", baseURL, kpTransactionID)
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", access_token))
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
}Response
{
"status": "queued",
"notification_id": "a1b2c3d4-0001-4000-8000-000000000001",
"kp_transaction_id": "txc_01kfktest001done"
}The notification is already waiting for the retry job, after an earlier replay or a failed delivery, so no new delivery is scheduled:
{
"status": "already_queued",
"notification_id": "a1b2c3d4-0001-4000-8000-000000000001",
"kp_transaction_id": "txc_01kfktest001done"
}{
"detail": "Invalid transaction ID format"
}{
"detail": "Notification not found"
}Batch Replay Notifications
POST /v2/notifications/replay
Queue notifications for replay by date range: up to 25 per request, the most recently created in the range. Each one queued is re-delivered to your webhook URL. If total_found is 25, the range may hold more: split it and replay each part.
A replay arrives 5 to 10 minutes later. queued means the notifications were scheduled for redelivery, not sent: a retry job, which runs every 5 minutes, delivers them 5 to 10 minutes after your request.
A batch replay may deliver a charge's notifications in any order: its done can arrive before its processing. Never let an older status overwrite a newer one: done wins over processing.
Request Body
| Name | Type | Description |
|---|---|---|
| start_date | datetime | Required. Start of date range (ISO 8601 format). Filters on the notification's creation. |
| end_date | datetime | Required. End of date range (ISO 8601 format). Filters on the notification's creation. |
| type | string | Required. Transaction type: pay, charge, or refund. |
Maximum date range is 24 hours. For longer periods, make multiple requests with different date ranges.
Example Request
const url = `${baseURL}/v2/notifications/replay`;
const body = {
start_date: "2026-01-15T00:00:00Z",
end_date: "2026-01-15T23:59:59Z",
type: "pay"
};
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body)
});import requests
url = f"{base_url}/v2/notifications/replay"
body = {
'start_date': '2026-01-15T00:00:00Z',
'end_date': '2026-01-15T23:59:59Z',
'type': 'pay'
}
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
}
response = requests.post(url, json=body, headers=headers)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := fmt.Sprintf("%s/v2/notifications/replay", baseURL)
requestBody, _ := json.Marshal(map[string]interface{}{
"start_date": "2026-01-15T00:00:00Z",
"end_date": "2026-01-15T23:59:59Z",
"type": "pay",
})
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")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
}Response
Notifications queued successfully
{
"status": "queued",
"total_found": 2,
"queued_count": 1,
"already_queued_count": 1,
"failed_count": 0,
"notifications": [
{
"notification_id": "b1b2c3d4-0001-4000-8000-000000000001",
"kp_transaction_id": "txc_01kfktest001done",
"status": "queued",
"error": null
},
{
"notification_id": "b1b2c3d4-0002-4000-8000-000000000002",
"kp_transaction_id": "txc_01kfktest002proc",
"status": "already_queued",
"error": null
}
]
}already_queued_count counts the notifications that were already waiting for the retry job, after an earlier replay or a failed delivery: no new delivery is scheduled for them.
Both date checks are reported on the request body as a whole: loc is ["body"], input is the body exactly as you sent it, and msg says which check failed. An end_date that isn't after start_date gets Value error, 'end_date' must be after 'start_date'. A 48-hour range returns:
{
"detail": [
{
"type": "value_error",
"loc": ["body"],
"msg": "Value error, Date range cannot exceed 24 hours (got 48.0 hours)",
"input": {
"start_date": "2026-01-15T00:00:00Z",
"end_date": "2026-01-17T00:00:00Z",
"type": "pay"
},
"ctx": {
"error": {}
}
}
]
}Response Status Values
| Status | Description |
|---|---|
queued | Notifications were successfully queued for replay. |
already_queued | Every notification found was already waiting for the retry job: no new delivery is scheduled. |
none_found | No notifications found matching the criteria. |
partial_failure | Some notifications failed to queue. Check individual notification errors. |