Donato.click
เว็บไซต์
หน้าแรก
แพลน
ระบบ Badge
สำหรับนักพัฒนา
ข้อกำหนดและนโยบาย
เริ่มรับ Donate ฟรี
Donato.click
TerminalDocsAPI keysopenapi.json
Terminal tourGet API key
API reference · generated from live code

Donato Developer Docs

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

openapi.jsonสร้าง API key
  • 1.0ตั้งค่าให้ AI
  • 1.1สองข้อที่ต้องรู้
  • 1.2Authentication
  • 2.0Endpoint reference
  • 2.1/analytics
  • 2.2/creator
  • 2.3/custom-domains
  • 2.4/donation-urls
  • 2.5/donations
  • 2.6/events
  • 2.7/goals
  • 2.8/keys
  • 2.9/presets
  • 2.10/usage
  • 2.11/wallet
  • 2.12/webhooks
  • 2.13/withdrawals
  • 3.1Event catalogue
  • 3.2Signature verification
  • 3.3Retry & auto-disable
  • 3.4Catch-up (/events)
  • 4.1Limits
  • 4.2CLI & MCP
1.0 · Start here

ให้ AI เขียน integration ให้

วางสองอย่างนี้ให้ Claude, Cursor หรือ ChatGPT ก่อนถามคำถามแรก — prompt สั้น ๆ ที่บอกกฎที่ integration พังบ่อยที่สุด และเอกสารทั้งหน้าในรูป Markdown

code
You are helping me integrate the Donato.click donation API.

Signed: IJe-Donato-click v1.0.1-1

Base URL: https://donato.click/api/v1
Sandbox:  https://sandbox-api.donato.click/api/v1
Full machine-readable schema: https://donato.click/api/v1/openapi.json
Auth: every request sends `Authorization: Bearer $DONATO_API_KEY`.

Rules that decide whether the integration is correct:
1. Webhooks are the truth. A 201 from POST /donations means "payment requested",
   not "money arrived". Only the `donation.completed` webhook means paid.
2. Money is satang (integer). 10000 = ฿100.00. Never parse amounts as floats.
3. Delivery guarantee: at-least-once — deduplicate on event_id
4. Verify every webhook before trusting it:
   - header: `X-IJe-Donato-click-Signature`
   - format: t=<unix-seconds>,v1.0.1-1=<hex hmac-sha256>
   - signed payload: `${timestamp}.${rawBody}` — sign the RAW body, not a re-serialised object.
   - reject anything older than 300s, and compare digests
     in constant time.
5. Reply 2xx within 10s. Failed attempts retry on
   1m → 5m → 15m → 1h → 24h, and an endpoint is disabled
   after 5 consecutive failures.
6. Send an `Idempotency-Key` on every write.

Webhook events: donation.completed, donation.refunded, withdraw.completed, ping.

Tooling you can use instead of writing HTTP by hand: the `donatoclick` CLI
(`donato init` scaffolds a receiver that already verifies signatures) and the
`donatoclick-mcp` MCP server, which can create endpoints and send test events.

When you are unsure about a route, read openapi.json rather than guessing.

ทั้ง prompt และ Markdown generate จาก openapi.json, รายการ event และ retry schedule ตัวเดียวกับที่หน้านี้ใช้ — โค้ดเปลี่ยนเมื่อไหร่ ข้อความที่คัดลอกก็เปลี่ยนตาม ไม่มีทางบอก AI ว่า header ลายเซ็นชื่ออะไรผิด · ถ้าอยากให้ agent ลงมือทำเองได้เลย (สร้าง endpoint, ยิง test, ไล่ event log) ใช้ MCP server ที่ 4.2 CLI & MCP · หรือพา AI ไปดูภาพรวมก่อนที่ terminal tour

1.1 · The two rules

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

ทุกอย่างที่เหลือคือรายละเอียด สองข้อนี้คือที่ที่ integration พังจริง — ภาพเดียวกันกับที่ terminal tour สอนด้วย donato check

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

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

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

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

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

ลองคำสั่งเดียวกันใน shell ได้ที่ cli → overview

1.2 · Authentication

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 — ให้เท่าที่ต้องใช้ ไม่ต้องให้ครบ

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

สร้าง key จริงที่ dashboard → developers

2.0 · Reference

Endpoint reference

33 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).
code
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).
code
curl -X GET "https://donato.click/api/v1/creator/profile" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/custom-domains

get/custom-domainsList custom domainsread

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).
code
curl -X GET "https://donato.click/api/v1/custom-domains" \
  -H "Authorization: Bearer $DONATO_API_KEY"
patch/custom-domainsVerify or remove a custom domainwrite

The id travels in the body, not the path. Action `verify` checks DNS and `remove` deletes.

Responses

  • 200 — Verified or removed
  • 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).
code
curl -X PATCH "https://donato.click/api/v1/custom-domains" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
post/custom-domainsAdd a custom domainwrite

Rise+ only. Max 3.

Responses

  • 201 — Domain added
  • 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).
code
curl -X POST "https://donato.click/api/v1/custom-domains" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'

/donation-urls

get/donation-urlsList donation linksread

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).
code
curl -X GET "https://donato.click/api/v1/donation-urls" \
  -H "Authorization: Bearer $DONATO_API_KEY"
patch/donation-urlsUpdate a donation linkwrite

Two shapes, both accepted. The `action` shape updates one thing per request; the multi-field shape updates everything in one request. Never mix `action: revoke` with other fields. min_amount is in BAHT here (the server multiplies by 100) — unlike the rest of the API, which counts integer satang. Tech debt: unify on satang.

Responses

  • 200 — Updated
  • 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).
code
curl -X PATCH "https://donato.click/api/v1/donation-urls" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
post/donation-urlsCreate a donation linkwrite

Rise+ only. Always starts offline — toggle online requires verification.

Responses

  • 201 — Link created
  • 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).
code
curl -X POST "https://donato.click/api/v1/donation-urls" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
get/donation-urls/check-slugCheck whether a custom slug is available

Public — no API key needed. Rate-limited.

Parameters

  • slug · queryrequired

Responses

  • 200 — Availability result
  • 400 — Missing slug
code
curl -X GET "https://donato.click/api/v1/donation-urls/check-slug" \
  -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).
code
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).
code
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).
code
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).
code
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).
code
curl -X GET "https://donato.click/api/v1/events" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/goals

delete/goalsDelete a goalwrite

The id travels as `?id=`, not as a path segment — there is no /goals/{id} route.

Parameters

  • id · queryrequired

Responses

  • 200 — Deleted
  • 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).
code
curl -X DELETE "https://donato.click/api/v1/goals" \
  -H "Authorization: Bearer $DONATO_API_KEY"
get/goalsList goalsread

All goals for the key owner.

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).
code
curl -X GET "https://donato.click/api/v1/goals" \
  -H "Authorization: Bearer $DONATO_API_KEY"
patch/goalsUpdate a goalwrite

The id travels in the body, not the path — there is no /goals/{id} route.

Responses

  • 200 — Updated
  • 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).
code
curl -X PATCH "https://donato.click/api/v1/goals" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
post/goalsCreate a goalwrite

Rise+ only — free is read-only. Idempotent by name+slug.

Responses

  • 201 — Goal created
  • 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).
code
curl -X POST "https://donato.click/api/v1/goals" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'

/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).
code
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
code
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).
code
curl -X DELETE "https://donato.click/api/v1/keys/{id}" \
  -H "Authorization: Bearer $DONATO_API_KEY"

/presets

delete/presetsDelete a widget presetwrite

The id travels in the ?id= query param — there is no /presets/{id} route.

Parameters

  • id · queryrequired

Responses

  • 200 — Deleted
  • 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).
code
curl -X DELETE "https://donato.click/api/v1/presets" \
  -H "Authorization: Bearer $DONATO_API_KEY"
get/presetsList widget presetsread

Empty canvas by default.

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).
code
curl -X GET "https://donato.click/api/v1/presets" \
  -H "Authorization: Bearer $DONATO_API_KEY"
patch/presetsUpdate a widget presetwrite

The id travels in the body, not the path — there is no /presets/{id} route.

Responses

  • 200 — Updated
  • 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).
code
curl -X PATCH "https://donato.click/api/v1/presets" \
  -H "Authorization: Bearer $DONATO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{}'
post/presetsCreate a widget preset (blank)write

Rise+ only — free is read-only. Empty canvas_elements.

Responses

  • 201 — Preset created
  • 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).
code
curl -X POST "https://donato.click/api/v1/presets" \
  -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).
code
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).
code
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).
code
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).
code
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).
code
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).
code
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).
code
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
code
curl -X POST "https://donato.click/api/v1/withdrawals" \
  -H "Authorization: Bearer $DONATO_API_KEY"
3.1 · Inbound

Webhook event catalogue

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

donation.completeddonation.refundedwithdraw.completedping
donation.refunded คือ event เดียวที่ “ถอนคืน” สิ่งที่ donation.completed เคยบอกไว้ — ยิงเมื่อเงินถูกคืนให้ผู้โดเนท หรือถูก dispute โดยธนาคาร ถ้า integration ของคุณปลดล็อครางวัลตอน donation.completed ต้องมี path ที่เก็บคืนด้วย data.reason บอกว่า refund หรือ dispute

ทุก delivery คือ POST ที่ body หน้าตาแบบนี้ และมาพร้อม header X-Donato-Event, X-Donato-Event-Id, X-Donato-Delivery-Attempt และ X-IJe-Donato-click-Signature · เห็นภาพเดียวกันแบบสั้น ๆ ที่ cli → webhooks

code
{
  "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 ที่ประมวลผลแล้วไว้ แล้วข้ามตัวซ้ำ อย่าใช้เวลาที่ได้รับเป็นตัวตัดสิน
3.2 · Security

Signature verification

Header X-IJe-Donato-click-Signature หน้าตาเป็น t=<unix-seconds>,v1.0.1-1=<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
code
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.0.1-1');
  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);
}

เริ่ม receiver ใหม่พร้อม verify ที่ถูกต้องด้วย donato init — ดูที่ cli → scaffold fast

3.3 · Delivery

Retry & auto-disable

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

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

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

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

3.4 · Recovery

Catch-up ด้วย GET /events

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

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

code
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 ปกติ เพราะมันถูกออกแบบมาให้โดนยิงรัว ๆ ตอนที่ทุกอย่างพังพอดี · ลองแบบไม่ต้องเขียนโค้ดที่ cli → events tail

4.1 · Quotas

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 คือลองใหม่ไม่ช่วย ต้องรอรอบบิลหรืออัปเกรด

4.2 · Tooling

CLI & MCP server

ของสองอย่างนี้ใช้ client layer ตัวเดียวกัน — อันหนึ่งให้คนพิมพ์ อีกอันให้ agent เรียก · ภาพรวมแบบ 60 วินาทีอยู่ที่ terminal tour

donato CLIdonatoclick

package ชื่อ donatoclick คำสั่งที่ติดตั้งมาชื่อ donato — Node 20 ขึ้นไป ไม่ต้องลง dependency อะไรเพิ่ม · ดูคำสั่งจริงใน terminal →

code
npm install -g donatoclick

# หรือไม่ต้องติดตั้ง — ต้องเรียกด้วยชื่อ package
npx donatoclick login

npx donato ไม่ใช่ตัวนี้ — donato บน npm เป็นของคนอื่น ชื่อ donato ใช้ได้หลังติดตั้งแล้วเท่านั้น

code
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 · ปล่อยเป็น donatoclick-mcp ไม่ต้องติดตั้งไว้ก่อน client เรียกผ่าน npx ได้เลย · ดู built-for-agents →

code
{
  "mcpServers": {
    "donato": {
      "command": "npx",
      "args": ["-y", "donatoclick-mcp"],
      "env": { "DONATO_API_KEY": "dnt_test_..." }
    }
  }
}

พร้อมแล้ว? สร้าง API key แล้วกลับมาเริ่มที่ terminal tour → overview

Donato.click

แพลตฟอร์มรับ Donate สำหรับครีเอเตอร์ไทย เริ่มต้นฟรี ไม่มีค่าแรกเข้า

ผลิตภัณฑ์

  • จุดเด่น
  • แพลน
  • ตราสัญลักษณ์

สำหรับครีเอเตอร์

  • เริ่มต้นใช้งาน
  • วิธีเชื่อมต่อสตรีม

สำหรับนักพัฒนา

  • อ่าน docs
  • ติดตั้ง CLI

ติดต่อ

  • hello@donato.click
© 2026 Donato.click
ข้อกำหนดและนโยบายความเป็นส่วนตัว