D
หน้าแรกหน้าแรก
จุดเด่น

ทั้งหน้าแรก

7 หัวข้อ ตั้งแต่สมัครยันถอนเงิน

07
01วิธีใช้งานสามขั้นตอน แล้วเปิดรับได้เลย02Live alert demoคนดูโอน — จอคุณเด้งทันที03ต่อ OBSต่อ OBS ใน 5 นาที ไม่ต้องมีความรู้เทคนิค04ค่าธรรมเนียมคิดตามจริง ไม่มีอะไรซ่อน05ครีเอเตอร์หน้ารับ Donate ที่เป็นของคุณเอง06แพลนเริ่มฟรีวันนี้ อัปเกรดเมื่อพร้อม07คำถามที่พบบ่อยยังไม่แน่ใจ?
ระบบ Badge →สำหรับนักพัฒนา
แพลน

แพลนและราคา

เริ่มฟรี อัปเกรดเมื่อรายได้เพิ่ม

฿199/เดือน
แพลน Pro฿199/เดือน
  • ค่าธรรมเนียม 4% แทน 6% ของ Free
  • ธีมและการแจ้งเตือนไม่จำกัด
  • Custom Domain และ Analytics
เปรียบเทียบทุกแพลน →เริ่มรับ Donate ฟรี
ระบบ Badgeระบบ Badge
สำหรับนักพัฒนาสำหรับนักพัฒนา
ข้อกำหนดและนโยบายข้อกำหนดและนโยบาย
ค้นหา...⌘K
เริ่มรับ Donate ฟรี
หน้าแรกจุดเด่นแพลนสำหรับนักพัฒนาเริ่มรับ Donate ฟรี

API reference

Donato Developer Docs

หน้านี้ generate จาก OpenAPI document, รายการ event และ retry schedule ตัวจริงที่ server ใช้งานอยู่ — ไม่ได้พิมพ์ตามมือ ถ้าโค้ดเปลี่ยน หน้านี้เปลี่ยนตาม

openapi.jsonสร้าง API key
  • เริ่มต้น
  • Authentication
  • Endpoint reference
  • Webhooks
  • Signature verification
  • Retry & auto-disable
  • Catch-up (/events)
  • Limits
  • CLI & MCP

สองข้อที่ตัดสินว่า integration ถูกหรือผิด

ทุกอย่างที่เหลือคือรายละเอียด สองข้อนี้คือที่ที่ integration พังจริง

1. Webhook คือความจริง

201 จาก POST /donations แปลว่า “ขอให้จ่าย” ไม่ใช่ “เงินเข้าแล้ว” รอ donation.completed เท่านั้น

2. เงินเป็นสตางค์จำนวนเต็ม

10000 = ฿100.00 อย่า parse เป็น float เด็ดขาด ปัดเศษครั้งเดียวก็ยอดไม่ตรงแล้ว

curl https://donato.click/api/v1/usage \
  -H "Authorization: Bearer dnt_live_..."

Authentication

Bearer token ธรรมดา แต่มีสองมิติที่พลาดกันบ่อย: environment ผูกกับ host ไม่ใช่กับ header และ scope ผูกกับ key ตอนสร้าง

PrefixHostใช้ทำอะไร
dnt_test_https://sandbox-api.donato.click/api/v1Sandbox — ทุกแพลนใช้ได้
dnt_live_https://donato.click/api/v1Production — ต้องแพลน Rise ขึ้นไป

เอา test key ไปยิง production host จะได้ 403 พร้อม type sandbox_only — จงใจให้เป็นแบบนี้ เพราะถ้าปล่อยให้สลับ environment ด้วย header หรือ query ได้ สุดท้ายจะมีคนเข้าใจว่าโดเนทจริงเป็นของทดสอบ

Scope มีสามระดับ: read · write · withdraw — ให้เท่าที่ต้องใช้ ไม่ต้องให้ครบ

curl https://donato.click/api/v1/donations \
  -H "Authorization: Bearer $DONATO_API_KEY"

Endpoint reference

18 operations — อ่านตรงจาก openapi.json ที่ server เสิร์ฟอยู่

/analytics

get/analytics/overviewRevenue KPIsread

Clamped to the plan analytics retention window.

Parameters

  • months · query

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/analytics/overview" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/creator

get/creator/profileThe key owner's profileread

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/creator/profile" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/donations

get/donationsList donationsread

Newest first, cursor-paginated.

Parameters

  • limit · query
  • cursor · query — Opaque cursor from a previous response. There is no offset paging.
  • status · query
  • from · query
  • to · query

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/donations" \
  -H "Authorization: Bearer $DONATO_API_KEY"
post/donationsCreate a donation requestwrite

Returns a payment URL. NOT a confirmation of payment — wait for the donation.completed webhook.

Parameters

  • Idempotency-Key · headerrequired — A UUID you generate per intent. Retrying with the same key replays the first response for 24h.

Responses

  • 201 — Donation request created
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 409 — Idempotency-Key reused with a different body
  • 422 — Amount outside the allowed range
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X POST "https://donato.click/api/v1/donations" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
get/donations/{id}Get one donationread

Parameters

  • id · pathrequired

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/donations/{id}" \
  -H "Authorization: Bearer $DONATO_API_KEY"
get/donations/statisticsDonation statisticsread

Totals bucketed by day, week, or month. Counts only revenue-bearing statuses, matching the creator dashboard.

Parameters

  • period · query
  • from · query
  • to · query

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/donations/statistics" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/events

get/eventsEvent log (catch-up feed)read

Every event this account produced, oldest first. Use it to recover events missed while your server was down: persist the last event_id you processed and pass it as `after`.

Parameters

  • after · query — Last event_id you processed.
  • event · query
  • limit · query

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/events" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/keys

get/keysList API keysread

Metadata only. A key is never readable after it is issued.

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/keys" \
  -H "Authorization: Bearer $DONATO_API_KEY"
post/keysCreate an API key — not available to API keyswrite

Returns 501. A key that could mint keys would survive revoking the original, so issuing is a dashboard-session operation.

Responses

  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
  • 501 — Dashboard session required
curl -X POST "https://donato.click/api/v1/keys" \
  -H "Authorization: Bearer $DONATO_API_KEY"
delete/keys/{id}Revoke an API keywrite

Idempotent, and allowed where creation is not: revoking narrows access. A key may revoke itself, after which the next call with it returns 401.

Parameters

  • id · pathrequired

Responses

  • 200 — Revoked
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X DELETE "https://donato.click/api/v1/keys/{id}" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/usage

get/usageCurrent limits and usageread

Exempt from the quotas it reports, so it stays callable when a quota is exhausted.

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/usage" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/wallet

get/walletWallet balanceread

Balances in satang.

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/wallet" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/webhooks

get/webhooksList webhook endpointsread

Signing secrets are never included — they are returned once, by POST, and never again.

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/webhooks" \
  -H "Authorization: Bearer $DONATO_API_KEY"
post/webhooksRegister a webhook endpointwrite

Returns the signing secret ONCE, in `data.secret`. Store it before you read anything else in the response. The environment is decided by the host you called, not by the body.

Responses

  • 201 — Endpoint registered; `data.secret` is present exactly this once
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X POST "https://donato.click/api/v1/webhooks" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
delete/webhooks/{id}Delete a webhook endpointwrite

Soft delete — the delivery log stays readable for its retention window.

Parameters

  • id · pathrequired

Responses

  • 200 — Deleted
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 404 — No such endpoint
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X DELETE "https://donato.click/api/v1/webhooks/{id}" \
  -H "Authorization: Bearer $DONATO_API_KEY"
post/webhooks/{id}/testSend a test eventwrite

Delivers a `ping` inline and returns the receiver's own status code and body. A non-2xx there is the answer, not an error on our side.

Parameters

  • id · pathrequired

Responses

  • 200 — Attempted — see `data.delivered`
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 404 — No such endpoint
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X POST "https://donato.click/api/v1/webhooks/{id}/test" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/withdrawals

get/withdrawalsWithdrawal historyread

Parameters

  • limit · query
  • cursor · query — Opaque cursor from a previous response. There is no offset paging.

Responses

  • 200 — Success
  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
curl -X GET "https://donato.click/api/v1/withdrawals" \
  -H "Authorization: Bearer $DONATO_API_KEY"
post/withdrawalsCreate a withdrawal — not available to API keyswithdraw

Returns 501. Withdrawals require a step-up (AAL2) check that an API key cannot present. Create them from the dashboard.

Responses

  • 400 — Invalid request
  • 401 — Invalid or revoked API key
  • 403 — Insufficient scope, plan, or a sandbox-only key aimed at production
  • 429 — Rate limit (type ends with 'rate_limited' — retry after the window) or monthly quota (type ends with 'quota_exceeded' — retrying will not help).
  • 501 — Step-up authentication required
curl -X POST "https://donato.click/api/v1/withdrawals" \
  -H "Authorization: Bearer $DONATO_API_KEY"

Webhook event catalogue

สมัครไว้กี่ event ก็ได้ ไม่เลือกเลย = รับทุกอัน การันตีแบบ at-least-once ให้ dedupe ด้วย event_id เสมอ

donation.completeddonation.refundedwithdraw.completedshop.order.createdshop.order.refundedping

ทุก delivery คือ POST ที่ body หน้าตาแบบนี้ และมาพร้อม header X-Donato-Event, X-Donato-Event-Id, X-Donato-Delivery-Attempt และ X-Donato-Signature

{
  "event": "donation.completed",
  "event_id": "evt_...",
  "created_at": "2026-01-31T09:12:44.120Z",
  "data": {
    "amount": 10000,
    "currency": "THB",
    "donor_name": "...",
    "message": "..."
  }
}
at-least-once แปลว่า event เดิมมาซ้ำได้จริง ๆ — เก็บ event_id ที่ประมวลผลแล้วไว้ แล้วข้ามตัวซ้ำ อย่าใช้เวลาที่ได้รับเป็นตัวตัดสิน

Signature verification

Header X-Donato-Signature หน้าตาเป็น t=<unix-seconds>,v1=<hex hmac-sha256> — ต้อง sign กับ raw body เท่านั้น

สิ่งที่ถูก sign คือ `${timestamp}.${rawBody}` — sign the RAW body, not a re-serialised object. ที่ต้องมี timestamp อยู่ในนั้นเพราะลายเซ็นที่ครอบแค่ body จะ valid ตลอดกาล — ใครดัก delivery ได้ครั้งเดียวก็ replay ได้ตลอดชีวิต ฝั่งรับต้องปฏิเสธอะไรที่เก่ากว่า 300 วินาที

กับดักอันดับหนึ่ง: ถ้า framework parse JSON ให้ก่อน แล้วเอา object มา JSON.stringify ใหม่ ลายเซ็นจะไม่ตรงทันที เพราะ byte ไม่เหมือนเดิม ต้องอ่าน raw body
import { createHmac, timingSafeEqual } from 'node:crypto';

const TOLERANCE_SECONDS = 300;

export function verify(rawBody: string, header: string, secret: string): boolean {
  const parts = new Map(
    header.split(',').map(p => {
      const [k, v] = p.split('=');
      return [k?.trim() ?? '', v?.trim() ?? ''] as const;
    })
  );

  const timestamp = Number(parts.get('t'));
  const provided = parts.get('v1');
  if (!Number.isFinite(timestamp) || !provided) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;

  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`, 'utf8')
    .digest('hex');

  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(provided, 'hex');
  // เช็คความยาวก่อน: timingSafeEqual โยน exception ถ้ายาวไม่เท่ากัน
  // และ exception ที่โยนออกมาก็เป็น timing signal ในตัวมันเอง
  return a.length === b.length && timingSafeEqual(a, b);
}

Retry & auto-disable

Timeout 10 วินาทีต่อครั้ง ลองใหม่ 5 รอบ แล้วหยุด

ครั้งแรก
→+1m#2
→+5m#3
→+15m#4
→+1h#5
→+24h#6

นับว่าสำเร็จเมื่อได้ 2xx เท่านั้น ถ้า endpoint พลาดติดกัน 5 ครั้ง ระบบจะปิดให้อัตโนมัติ แล้วขึ้นเหตุผลไว้ในหน้า Developers — เปิดใหม่ได้เมื่อแก้เสร็จ (การเปิดใหม่จะรีเซ็ตตัวนับให้ด้วย)

ถ้าปิดไปแล้วหรือ server ล่มนานกว่า retry window อย่ามานั่งเดาว่าพลาดอะไรไป — ใช้ /events ดึงย้อนหลัง

Catch-up ด้วย GET /events

Webhook คือ push, endpoint นี้คือ pull — มีไว้สำหรับตอนที่ server คุณล่มไปสองชั่วโมงแล้วโดเนทช่วงนั้นหายไปจากสายตา

เก็บ event_id ตัวสุดท้ายที่ประมวลผลสำเร็จไว้ แล้วส่งเป็น after ตอน start ขึ้นมาใหม่ endpoint นี้เรียงจากเก่าไปใหม่ (ต่างจาก list endpoint อื่นทั้งหมด) เพราะการ replay ต้องเป็นลำดับเดียวกับที่มันเกิดขึ้นจริง

let cursor = await loadLastSeenEventId(); // null ครั้งแรก
let hasMore = true;

while (hasMore) {
  const url = new URL('https://donato.click/api/v1/events');
  url.searchParams.set('limit', '100');
  if (cursor) url.searchParams.set('after', cursor);

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.DONATO_API_KEY}` },
  });
  const { data, meta } = await res.json();

  for (const event of data) await handle(event);

  cursor = meta.next_after;
  hasMore = meta.has_more;
  await saveLastSeenEventId(cursor); // save หลัง handle เสมอ ไม่ใช่ก่อน
}

Rate limit ของ endpoint นี้ตั้งไว้เป็นสองเท่าของ read ปกติ เพราะมันถูกออกแบบมาให้โดนยิงรัว ๆ ตอนที่ทุกอย่างพังพอดี

Limits

ตัวเลขชุดนี้อ่านจาก constant เดียวกับที่ server บังคับใช้จริง

Rate limit (req/min)freerisepeak
sandbox6060120
read—60300
write—20100
withdrawal——5
events—120600
alerts—3060
realtime—1030
promptpay—60300
slip—1030
Quotafreerisepeak
API keys1210
Webhook endpoints1310
Webhook replays / month00100
Webhook log retention (days)1730
Event log retention (days)13090

โดน 429 แล้วดู type ใน problem ให้ดี: ลงท้ายด้วย rate_limited คือรอแล้วลองใหม่ได้, ลงท้ายด้วย quota_exceeded คือลองใหม่ไม่ช่วย ต้องรอรอบบิลหรืออัปเกรด

CLI & MCP server

ของสองอย่างนี้ใช้ client layer ตัวเดียวกัน — อันหนึ่งให้คนพิมพ์ อีกอันให้ agent เรียก

donato CLI

ยังไม่ปล่อยบน npm

จะปล่อยเป็น @donato-click/cli (คำสั่งชื่อ donato) ตอนนี้อยู่ใน repo ที่ packages/cli — build ด้วย npx tsc -b packages/cli แล้วรัน node packages/cli/dist/index.js

donato login                       # เก็บ key ไว้ที่ ~/.donato/config.json
donato keys list
donato webhooks create https://your-server.com/hook
donato webhooks test <id>
donato events tail
donato listen --forward localhost:3000/api/donato
donato init                        # scaffold receiver ที่ verify ลายเซ็นให้แล้ว

MCP server

ให้ AI agent ตั้ง integration ให้เสร็จเองได้ตั้งแต่ต้นจนจบ — list key, สร้าง endpoint, ยิง test, ไล่ event log · จะปล่อยเป็น @donato-click/mcp ระหว่างนี้ชี้ไปที่ไฟล์ใน repo ได้เลย

{
  "mcpServers": {
    "donato": {
      "command": "node",
      "args": ["<repo>/packages/mcp/dist/index.js"],
      "env": { "DONATO_API_KEY": "dnt_test_..." }
    }
  }
}