Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 36 additions & 25 deletions ecosystem/ton-pay/webhooks.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
---
title: "Webhooks"
title: "TON Pay webhooks"
sidebarTitle: "Webhooks"
tag: "beta"
---

import { Aside } from '/snippets/aside.jsx';

<Aside
type="caution"
title="Beta version"
>
Webhook functionality is in closed beta. Interfaces or behavior may change in the future.
</Aside>

TON Pay uses webhooks to notify applications in real-time about transfer events.

## How webhooks work
Expand All @@ -25,9 +33,9 @@ TON Pay uses webhooks to notify applications in real-time about transfer events.

Configure the endpoint in TON Pay Merchant Dashboard.

1. Open the [TON Pay Merchant Dashboard](https://tonpay.tech/dashboard) and sign in to the merchant account.
1. Navigate to the <kbd>Developer</kbd> <kbd>Webhooks</kbd> sections.
1. Enter the webhook URL. In production, the endpoint must use HTTPS. For example, `https://thedomain.com/webhooks/tonpay`
1. Open the TON Pay Merchant Dashboard and sign in to the merchant account.
1. Navigate to the <kbd>Developer</kbd> section, then select <kbd>Webhooks</kbd>.
1. Enter the webhook URL. In production, the endpoint must use HTTPS. For example, `https://thedomain.com/webhooks/tonpay`.
1. Save the configuration and use the test feature to verify delivery.

<Aside
Expand All @@ -49,8 +57,10 @@ import {
type TransferRefundedWebhookPayload, // Coming soon
} from "@ton-pay/api";

// Event types: "transfer.completed" | "transfer.refunded" (Coming Soon)
type WebhookEventType = "transfer.completed" | "transfer.refunded";
// Event types
type WebhookEventType =
| "transfer.completed"
| "transfer.refunded"; // Coming soon

// Union type for all webhook payloads
type WebhookPayload =
Expand Down Expand Up @@ -343,6 +353,7 @@ Validate fields against the expected transaction data before marking an order as
```

1. Verify the event type.

```ts
if (webhookData.event !== "transfer.completed") {
return res.status(400).json({ error: "Unexpected event type" });
Expand Down Expand Up @@ -499,7 +510,7 @@ TON Pay retries failed webhook deliveries.
title="Retry delays"
icon="clock"
>
1s → 5s → 15s
1s, 5s, and 15s.
</Card>
</CardGroup>

Expand Down Expand Up @@ -536,22 +547,22 @@ TON Pay retries failed webhook deliveries.
- Return `2xx` only after successful validation, then process the webhook to avoid timeouts.

```ts
app.post('/webhook', async (req, res) => {
// Validate signature first
if (!verifyWebhookSignature(...)) {
return res.status(401).json({ error: 'Invalid signature' });
}
app.post("/webhook", async (req, res) => {
// Validate signature first
if (!verifyWebhookSignature(...)) {
return res.status(401).json({ error: "Invalid signature" });
}

// Quick validations
if (!isValidWebhook(req.body)) {
return res.status(400).json({ error: 'Invalid webhook data' });
}
// Quick validations
if (!isValidWebhook(req.body)) {
return res.status(400).json({ error: "Invalid webhook data" });
}

// Acknowledge after validation
res.status(200).json({ received: true });
// Acknowledge after validation
res.status(200).json({ received: true });

// Process in background
processWebhookAsync(req.body).catch(console.error);
// Process in background
processWebhookAsync(req.body).catch(console.error);
});
```

Expand All @@ -562,15 +573,15 @@ TON Pay retries failed webhook deliveries.
const order = await db.getOrder(payload.reference);

// Check if already processed
if (order.status === 'completed') {
console.log('Order already completed:', payload.reference);
return; // Skip processing
if (order.status === "completed") {
console.log("Order already completed:", payload.reference);
return; // Skip processing
}

// Process and update status atomically
await db.transaction(async (tx) => {
await tx.updateOrder(order.id, { status: 'completed' });
await tx.createPaymentRecord(payload);
await tx.updateOrder(order.id, { status: "completed" });
await tx.createPaymentRecord(payload);
});
}
```
Expand Down
Loading