Server Integration
Integrate DonutMe payments directly using the REST API — no SDK required.
Overview
DonutMe provides a straightforward REST API for server-to-server integration. You can create checkout sessions, query transactions, and verify webhook signatures using standard HTTP requests in any language.
Coming Soon: A TypeScript SDK is planned for the future. In the meantime, use the REST API directly or leverage AI-assisted integration via our Copilot Skill.
Authentication
All authenticated API requests carry your API key in the Authorization header as a Bearer token:
curl -X GET https://api.donutme.xyz/api/v1/transactions \
-H "Authorization: Bearer dm_your_api_key_here"Generate API keys from the Dashboard → Account → API Keys.
See Authentication for full details on scopes and key management.
Create a Checkout Session
The checkout session endpoint is public (no API key required) — your server or client can create sessions directly:
// Node.js / TypeScript example
const response = await fetch("https://api.donutme.xyz/api/v1/checkout/sessions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
paymentPlanId: "your-payment-plan-id",
email: "customer@example.com", // optional
metadata: { orderId: "order_12345" }, // optional
}),
});
const { data } = await response.json();
// data.token → use to build checkout URL
// Redirect user to: https://donutme.xyz/pay/{planId}/checkout?session={data.token}Python
import requests
resp = requests.post("https://api.donutme.xyz/api/v1/checkout/sessions", json={
"paymentPlanId": "your-payment-plan-id",
"email": "customer@example.com",
})
session = resp.json()["data"]
checkout_url = f"https://donutme.xyz/pay/{plan_id}/checkout?session={session['token']}"Query Transactions
Requires API key with transactions:read scope:
const response = await fetch(
"https://api.donutme.xyz/api/v1/projects/{projectId}/transactions?status=confirmed&page=1&limit=20",
{ headers: { Authorization: "Bearer dm_xxx" } }
);
const { data, meta } = await response.json();
// data: Transaction[]
// meta.pagination: { page, limit, total, totalPages }Verify Webhook Signatures
When receiving webhooks, verify the HMAC-SHA256 signature to ensure authenticity:
import crypto from "node:crypto";
function verifyWebhookSignature(
payload: string,
signatureHeader: string,
secret: string
): boolean {
// Header format: "v1,sha256=<hex-digest>"
const match = signatureHeader.match(/^v1,sha256=([0-9a-f]{64})$/);
if (!match) return false;
const received = match[1];
const expected = crypto
.createHmac("sha256", secret)
.update(payload, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(received),
Buffer.from(expected)
);
}
// In your webhook handler:
app.post("/webhook", (req, res) => {
const signature = req.headers["x-donutme-signature"];
const isValid = verifyWebhookSignature(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}
const { event, data } = req.body;
switch (event) {
case "payment.confirmed":
// Fulfill the order
break;
case "payment.failed":
// Handle failure
break;
}
res.status(200).json({ received: true });
});Python
import hmac
import hashlib
import re
def verify_signature(payload: bytes, signature_header: str, secret: str) -> bool:
"""Verify DonutMe webhook signature (format: v1,sha256=<hex>)."""
match = re.match(r"^v1,sha256=([0-9a-f]{64})$", signature_header)
if not match:
return False
received = match.group(1)
expected = hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(received, expected)Error Handling
All API responses use a consistent envelope:
// Success
{
"success": true,
"statusCode": 200,
"data": { ... },
"meta": { "timestamp": "...", "requestId": "req_..." }
}
// Error
{
"success": false,
"statusCode": 422,
"message": "Validation failed",
"error": { "code": "VALIDATION_ERROR", "details": [...] }
}Common error codes:
| Code | Description |
|---|---|
VALIDATION_ERROR | Invalid request body or parameters |
RESOURCE_NOT_FOUND | Plan, session, or transaction not found |
RATE_LIMITED | Too many requests — check Retry-After header |
INSUFFICIENT_SCOPES | API key lacks required scope |
PAYMENT_PLAN_INACTIVE | Plan is not active |
PROJECT_SUSPENDED | Project is suspended or archived |
Rate Limits
| Endpoint Category | Limit |
|---|---|
| Checkout session creation | 5 req/min per IP |
| Authenticated reads (list/get) | 60 req/min per key |
| Authenticated writes | 30 req/min per key |
| Webhook verification | Unlimited |
When rate limited, the response includes a Retry-After header (in seconds).
Next Steps
- Embed Checkout — iframe integration with postMessage events
- Webhooks — Full webhook event reference and payload format
- API Authentication — API key scopes and management
- API Payments — Payment-specific endpoints
