kamiPay LogokamiPay Docs

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

NameTypeDescription
e2e_idstringBank e2e_id (E* for payments, D* for refunds). Min 20 characters.
transaction_idstringKamiPay 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_…).
statusstringFilter 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.
typestringRequired. pay (payout), charge (pay-in), or refund. Without it, the request is rejected with 422.
from_datedatetimeStart date filter (ISO 8601 format). Filters on the notification's last_updated_at, which every delivery, retry and replay renews.
to_datedatetimeEnd date filter (ISO 8601 format). Filters on the notification's last_updated_at, which every delivery, retry and replay renews.
limitintMaximum results to return (1-100). Default: 25.
offsetintPagination 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

NameTypeDescription
kp_transaction_idstringRequired. 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

NameTypeDescription
typestringTransaction 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_* or cqr_* (a charge's kamipay_id, cqr_ for a printed sale) and ptxr_* (its operation_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

NameTypeDescription
kp_transaction_idstringRequired. 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

NameTypeDescription
kp_transaction_idstringRequired. 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

NameTypeDescription
start_datedatetimeRequired. Start of date range (ISO 8601 format). Filters on the notification's creation.
end_datedatetimeRequired. End of date range (ISO 8601 format). Filters on the notification's creation.
typestringRequired. 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

StatusDescription
queuedNotifications were successfully queued for replay.
already_queuedEvery notification found was already waiting for the retry job: no new delivery is scheduled.
none_foundNo notifications found matching the criteria.
partial_failureSome notifications failed to queue. Check individual notification errors.

On this page