Webhooks
Receive real-time notifications when payment and checkout events occur. DonutMe sends HTTPS POST requests with a signed JSON payload to your configured endpoint.
Setting Up Webhooks
- Navigate to Project Settings → Webhooks
- Enter your HTTPS endpoint URL and click Save
- Generate a signing secret via Generate / Rotate Secret
- (Optional) Filter which events you want to receive
Event Types
| Event | Description |
|---|---|
payment.confirmed | Payment confirmed on-chain |
payment.failed | Payment transaction failed |
checkout.session_created | New checkout session initiated |
checkout.session_completed | Checkout session completed with a confirmed payment |
checkout.session_expired | Checkout session expired without payment |
Webhook Payload
Every webhook delivery is an HTTPS POST with Content-Type: application/json.
{
"id": "evt_...",
"event": "payment.confirmed",
"timestamp": 1775815200000,
"data": {
"projectId": "uuid",
"transactionId": "uuid",
"txHash": "0x...",
"networkId": "uuid",
"assetId": "uuid",
"token": "USDC",
"amount": "10.00",
"grossAmount": "10.00",
"feeAmount": "0.10",
"netAmount": "9.90",
"status": "confirmed",
"confirmedAt": "2026-04-08T10:00:00.000Z",
"paymentPlanId": "uuid",
"customerId": "uuid",
"customerEmail": "buyer@example.com",
"customerName": "Jane Doe",
"metadata": {}
}
}Payload fields vary by event type. All payloads include id, event, timestamp (epoch milliseconds), and a data object. The id field is a stable event identifier (also sent as the X-DonutMe-Event-Id header) — deduplicate on it, since retries reuse the same id.
Signature Verification
Every request includes an X-DonutMe-Signature header in the format:
v1,sha256=<hex-digest>Verify by computing HMAC-SHA256 of the raw request body using your signing secret:
import crypto from "crypto";
function verifySignature(body, secret, signatureHeader) {
const [, hash] = signatureHeader.split("sha256=");
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(hash, "hex"),
Buffer.from(expected, "hex"),
);
}When you rotate your signing secret, both the old and new secrets are valid for 24 hours to allow a graceful migration.
Retry Policy
Failed deliveries (5xx responses, network errors, or timeouts) are retried with exponential backoff. By default, up to 8 attempts are made with a 10-second base delay:
| Attempt | Delay |
|---|---|
| 1st retry | 10 seconds |
| 2nd retry | 20 seconds |
| 3rd retry | 40 seconds |
| 4th retry | 80 seconds |
| 5th retry | 160 seconds |
| 6th retry | 320 seconds |
| 7th retry | 640 seconds |
After the configured number of attempts (8 by default), the delivery is marked as permanently failed.
4xx responses (other than 429) are treated as permanent client errors and are not retried. A 429 is retried honoring the Retry-After header. The per-attempt request timeout is 10 seconds by default.
Circuit Breaker
If your endpoint accumulates 15 consecutive failures, webhook delivery is automatically disabled for that project. You can re-enable it from the Webhooks settings page once the issue is resolved.
Test Events
Use the Send Test Event button in the Webhooks settings to send a webhook.test event to your endpoint. This verifies connectivity without affecting real data.
Delivery Logs
View webhook delivery history in Project Settings → Webhooks → Delivery Logs. Each log entry shows:
- HTTP status code
- Response latency
- Event type
- Retry attempt number
- Error details (if any)
