developers

Approve orders from your own code.

Ask the PaidHook API whether a payment has arrived, or let PaidHook post every new transaction to your server. One API key, plain HTTPS and JSON.

Check a payment

One call: “has Rp 50,000 arrived?” Optionally ask the bank for a fresh check and wait for it.

Webhooks

PaidHook posts each new transaction to your HTTPS endpoint, signed with your secret.

Transaction feed

List incoming and outgoing transactions with filters and cursor paging.

Per-account keys

Create and revoke keys in the dashboard. Keys are stored hashed and shown only once.

Authentication

Create a key in the dashboard under API keys. Keys start with ph_live_ and are shown once. Send the key in the X-API-Key header (or as Authorization: Bearer ph_live_…). All requests go to:

https://paidhook.com/v1

Call the API from your server only. Never put an API key in a web page or mobile app.

Check a payment

GET /mutations/check: did a payment of this exact amount arrive recently? This is the call most shops use to approve an order.

curl -H "X-API-Key: ph_live_…" \
  "https://paidhook.com/v1/mutations/check?amount=50000&refresh=true&wait=90"
ParameterDescription
amount requiredExact amount to look for, e.g. 50000.
typeCR (money in, default) or DB (money out).
within_minutesOnly rows PaidHook received in this window. Default 1440, max 10080.
bankDestination bank only, e.g. bca, bri or myBCA.
account_numberDestination account number only.
device_idOne connected account only.
refreshtrue asks the connected bank for a fresh check first. For an account read by PaidHook Operator, the phone is read right away.
waitWith refresh: seconds to wait for that check, up to 120.

Response:

{
  "found": true,
  "matches": [ { "id": 1, "type": "CR", "amount": 50000.0, "date": "2026-10-29", … } ],
  "last_sync_at": "2026-10-29T04:12:08Z",
  "requests": [ { "id": 7, "status": "done", … } ],
  "refresh_skipped": []
}

List transactions

GET /mutations: transactions, newest first. Filter with type, date_from/date_to (YYYY-MM-DD), min_amount/max_amount, q (text in the bank description), received_after and device_id. Page with limit (max 200) and pass next_before_id as before_id for older rows.

curl -H "X-API-Key: ph_live_…" "https://paidhook.com/v1/mutations?type=CR&limit=20"

Each transaction includes the bank description split into fields in details: counterparty_name, counterparty_bank, channel, transfer_ref, note and the original lines.

Connected accounts

GET /devices: your connected bank accounts and whether each is running. status is one of active, needs_bank_login, paused, stopped or offline.

curl -H "X-API-Key: ph_live_…" "https://paidhook.com/v1/devices"

Fresh bank check

POST /devices/{id}/sync asks one connected account for a fresh check, at most once every 2 minutes per account. Follow it with GET /sync-requests/{id} until status is done or failed. A refused request returns 409 with the reason: offline, not_active, pending or cooldown.

Webhooks

Add an HTTPS endpoint under API keys → Webhooks (up to 5). PaidHook sends a POST with this JSON body for each new transaction. Reply with any 2xx status within 10 seconds.

{
  "id": "evt_8f2c…",
  "type": "transaction.created",
  "created_at": "2026-11-02T11:08:40Z",
  "data": {
    "id": 1, "device_id": 2, "institution": "KlikBCA", "account_number": "8201299047",
    "date": "2026-10-29", "type": "CR", "amount": 10000.0, "balance": 20000.0,
    "description": "BI-FAST CR TRANSFER DR 542 …",
    "details": { "kind": "transfer", "counterparty_bank": "Bank Jago", … }
  }
}
  • Headers: Paidhook-Event, Paidhook-Event-Id and Paidhook-Signature.
  • Failed deliveries are retried after 30 s, 2 min, 10 min, 1 h, 6 h and 16 h, then marked failed. The id stays the same, so use it to ignore repeats.
  • The dashboard can send a ping test event to check your endpoint.

Verify signatures

Paidhook-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256>, computed over "<t>." + raw body with your endpoint secret (whsec_…). Recompute it over the raw body and reject old timestamps.

PHP

$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_PAIDHOOK_SIGNATURE']), $sig);
$expected = hash_hmac('sha256', $sig['t'] . '.' . $body, 'whsec_…');
if (!hash_equals($expected, $sig['v1']) || abs(time() - (int) $sig['t']) > 300) {
    http_response_code(400); exit;
}
$event = json_decode($body, true);  // mark the order as paid here
http_response_code(200);

Python

import hmac, hashlib, time
parts = dict(p.split("=", 1) for p in request.headers["Paidhook-Signature"].split(","))
expected = hmac.new(b"whsec_…", f"{parts['t']}.".encode() + request.body, hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, parts["v1"]) and abs(time.time() - int(parts["t"])) < 300

Errors

StatusMeaning
401Missing, invalid or revoked API key.
404No connected account matches device_id, bank or account_number.
409A fresh check was refused (see Fresh bank check).
422A parameter has the wrong format.

The complete, always-current list of fields is in the API reference.