Skip to content

Commit 2a9063d

Browse files
committed
docs: cleanup UB docs
1 parent 2c6bd62 commit 2a9063d

File tree

23 files changed

+218
-281
lines changed

23 files changed

+218
-281
lines changed

apps/portal/src/app/connect/pay/overview/page.mdx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ export const metadata = createMetadata({
1313
"Learn everything about thirdweb’s web3 payments solution, Universal Bridge. Technical docs on onramping, bridging + swapping.",
1414
});
1515

16-
# Overview
16+
# Universal Bridge
1717

18-
Universal Bridge allows your users to bridge, swap, and purchase cryptocurrencies and execute transactions with any fiat options or tokens via cross-chain routing. It enables users to purchase or complete in-app transactions with any token they hold.
18+
Universal Bridge allows you to create both simple and advanced payment flows for bridging, swapping, onramping, and peer-to-peer purchases. It's been used to drive millions in NFT sales, bridge native tokens to brand new chains, send stablecoins between users, and more. To get started check out the [SDK functions](https://portal.thirdweb.com/typescript/v5/buy/quote), [API reference](https://bridge.thirdweb.com/reference), or [playground](https://playground.thirdweb.com/connect/pay).
1919

2020
<DocImage src={PayOverviewImage} />
2121

@@ -35,7 +35,7 @@ Universal Bridge allows your users to bridge, swap, and purchase cryptocurrencie
3535
description="Bridging support in over 160+ countries to reach a truly global audience."
3636
iconUrl={<Globe/>}
3737
/>
38-
<FeatureCard
38+
<FeatureCard
3939
title="Earn revenue"
4040
description="Monetize your application and earn swap fees on each transaction."
4141
iconUrl={<PiggyBank />}
@@ -80,7 +80,6 @@ Universal Bridge is supported on select EVM compatible chains. To view the full
8080
| Provider | Supported Countries |
8181
| -------- | ------------------------------------------------------------------------------------------------------- |
8282
| Transak | https://transak.notion.site/On-Ramp-Payment-Methods-Fees-Other-Details-b0761634feed4b338a69f4f186d906a5 |
83-
| Kado | https://www.kado.money/supported-countries |
8483
| Stripe | https://docs.stripe.com/crypto/onramp |
8584
| Coinbase | https://docs.cdp.coinbase.com/onramp/docs/payment-methods/ |
8685

@@ -91,4 +90,5 @@ Universal Bridge is supported on select EVM compatible chains. To view the full
9190
| [Buy Crypto](https://playground.thirdweb.com/connect/pay/) | Developers who want onramp and crypto purchase experiences directly in their application. |
9291
| [Transactions](https://playground.thirdweb.com/connect/pay/transactions) | Developers who want users to onramp or purchase crypto directly into a transaction. Great for minting and NFT purchase flows. |
9392
| [In-App Purchases](https://playground.thirdweb.com/connect/pay/commerce) | Developers who want to take payments from Fiat or Crypto directly to a seller wallet |
94-
| [Headless](https://playground.thirdweb.com/connect/pay/backend) | Developers who prefer a headless customized flow using APIs. |
93+
| [SDK](https://portal.thirdweb.com/typescript/v5/buy/quote) | Build your own UI with the SDK. |
94+
| [API](https://playground.thirdweb.com/connect/pay/backend) | Control the full experience with the API. |

apps/portal/src/app/connect/pay/webhooks/page.mdx

Lines changed: 80 additions & 241 deletions
Original file line numberDiff line numberDiff line change
@@ -4,279 +4,118 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
44
export const metadata = createMetadata({
55
title: "Universal Bridge Webhooks Implementation Guide — thirdweb Docs",
66
description:
7-
"Learn how to set up webhooks for web3 payments with thirdweb’s Universal bridge feature: the technical docs",
7+
"Learn how to set up webhooks for any Universal Bridge transactions.",
88
});
99

1010
# Webhooks
1111

12-
Universal Bridge can be configured to send webhook events to notify your application any time an event happens on your transaction. Universal Bridge sends a response, via a HTTP request, to any endpoint URLs that you have provided us in your Team > Project > Connect > Universal Bridge > Webhooks page in [thirdweb dashboard](https://thirdweb.com/team).
12+
You can create a webhook in your project dashboard under the Universal Bridge tab. You'll be prompted to copy a secret key before saving the webhook. This will be used for verification on all webhook requests received by your backend.
1313

14-
## Events
14+
## Response Objects
1515

16-
To listen to events, create a webhook in your Team > Project > Connect > Pay > Webhooks page in [thirdweb dashboard](https://thirdweb.com/team). Webhook URLs must start with `https://`.
16+
To get the TypeScript type for webhook responses, see the [`Webhook.Payload`](https://portal.thirdweb.com/typescript/v5/webhook/payload) type in the SDK.
1717

18-
| Event | Description |
19-
| ------------------- | ----------------------------------- |
20-
| `purchase_complete` | A transaction is confirmed onchain. |
18+
### Example Payloads
2119

22-
### Purchase Complete
23-
24-
Triggered when a transaction is confirmed onchain. This event provides information about the new status of the order and its transactionHash, as well as other relevant information.
25-
26-
Example Response:
27-
28-
<Tabs defaultValue="fiat">
20+
<Tabs defaultValue="bridge">
2921

3022
<TabsList>
31-
<TabsTrigger value="fiat">Fiat Purchase</TabsTrigger>
32-
<TabsTrigger value="crypto">Crypto Purchase</TabsTrigger>
23+
<TabsTrigger value="bridge">Bridge & Swap</TabsTrigger>
24+
<TabsTrigger value="onramp">Onramp</TabsTrigger>
3325
</TabsList>
34-
35-
<TabsContent value='fiat'>
26+
<TabsContent value='onramp'>
3627
```json
3728
{
29+
"version": 2,
3830
"data": {
39-
"buyWithFiatStatus": {
40-
"intentId": "f4cf8ab7-bb62-4b3b-a180-70fc7d72446c",
41-
"status": "ON_RAMP_TRANSFER_COMPLETED",
42-
"toAddress": "0xebfb127320fcbe8e07e5a03a4bfb782219f4735b",
43-
"quote": {
44-
"createdAt": "2024-06-18T23:46:46.024Z",
45-
"fromCurrency": {
46-
"amountUnits": "279",
47-
"amount": "2.79",
48-
"currencySymbol": "USD",
49-
"decimals": 2,
50-
"amountUSDCents": 279
51-
},
52-
"fromCurrencyWithFees": {
53-
"amountUnits": "294",
54-
"amount": "2.94",
55-
"currencySymbol": "USD",
56-
"decimals": 2,
57-
"amountUSDCents": 279
58-
},
59-
"onRampToken": {
60-
"chainId": 137,
61-
"tokenAddress": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
62-
"name": "Matic",
63-
"symbol": "MATIC",
64-
"decimals": 18,
65-
"priceUSDCents": 54.797200000000004
66-
},
67-
"toToken": {
68-
"chainId": 137,
69-
"tokenAddress": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
70-
"name": "Matic",
71-
"symbol": "MATIC",
72-
"decimals": 18,
73-
"priceUSDCents": 54.797200000000004
74-
},
75-
"estimatedOnRampAmountWei": "5000000000000000000",
76-
"estimatedOnRampAmount": "5",
77-
"estimatedToTokenAmount": "5",
78-
"estimatedToTokenAmountWei": "5000000000000000000",
79-
"estimatedDurationSeconds": 30
80-
},
81-
"source": {
82-
"completedAt": "2024-06-18T23:49:00.347Z",
83-
"amount": "5",
84-
"amountWei": "5000000000000000000",
85-
"token": {
86-
"chainId": 137,
87-
"tokenAddress": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
88-
"name": "Matic",
89-
"symbol": "MATIC",
90-
"decimals": 18,
91-
"priceUSDCents": 54.797200000000004
92-
},
93-
"transactionHash": "0x4bb089f6a60b49235a817b52bf39bc078f1246df15731b85837526bb62cf4e70",
94-
"explorerLink": "https://polygonscan.com/tx/0x4bb089f6a60b49235a817b52bf39bc078f1246df15731b85837526bb62cf4e70",
95-
"amountUSDCents": 275
96-
}
97-
}
31+
"id": "d5d33244-f855-441d-af62-4593094a3eb1",
32+
"onramp": "stripe",
33+
"token": {
34+
"chainId": 8453,
35+
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
36+
"symbol": "USDC",
37+
"name": "USD Coin",
38+
"decimals": 6,
39+
"priceUsd": 0.99995,
40+
"iconUri": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694"
41+
},
42+
"amount": "5100000",
43+
"currency": "USDC",
44+
"currencyAmount": 5.36,
45+
"receiver": "0xa5a484Af10FF67257A06DDbf8DdE6A99a483f098",
46+
"status": "PENDING",
47+
"purchaseData": null
9848
}
9949
}
10050
```
10151
</TabsContent>
10252

103-
<TabsContent value='crypto'>
53+
<TabsContent value='bridge'>
10454
```json
10555
{
56+
"version": 2,
10657
"data": {
107-
"buyWithCryptoStatus": {
108-
"swapType": "SAME_CHAIN",
109-
"source": {
110-
"transactionHash": "0x74d6c619a09e78f03f4bd495f29d5937a2539d0bbe8973e7710dce3e88c30b8b",
111-
"token": {
112-
"chainId": 10,
113-
"tokenAddress": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
114-
"decimals": 18,
115-
"name": "ETH",
116-
"symbol": "ETH",
117-
"priceUSDCents": 346529
118-
},
119-
"amountWei": "318486512146714",
120-
"amount": "0.000318486512146714",
121-
"amountUSDCents": 110,
122-
"completedAt": "2024-06-18T23:44:07.000Z"
58+
"transactionId": "0x7baae858e28628fe57cb0ca93c86fcda68f556563199cb4472044bfd9fbe5ec8",
59+
"paymentId": "0xbea711bf1da223b29b176cff7f01596834dd63c7ad85477a3504f4b9285b33a2",
60+
"clientId": "c56b27030ad22846003fafbb4302b5d7",
61+
"action": "SELL",
62+
"status": "COMPLETED",
63+
"originToken": {
64+
"chainId": 466,
65+
"address": "0x675C3ce7F43b00045a4Dab954AF36160fb57cB45",
66+
"symbol": "USDC",
67+
"name": "USD Coin",
68+
"decimals": 6,
69+
"priceUsd": 0.99995,
70+
"iconUri": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694"
71+
},
72+
"originAmount": "24875000",
73+
"destinationToken": {
74+
"chainId": 8453,
75+
"address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
76+
"symbol": "USDC",
77+
"name": "USD Coin",
78+
"decimals": 6,
79+
"priceUsd": 0.99995,
80+
"iconUri": "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694"
81+
},
82+
"destinationAmount": "24794905",
83+
"sender": "0xb4523A0D69612B9A4629A70E42021B2F384CC8Fa",
84+
"receiver": "0x044A83bA68f36CF1F27836Cb93614f86d8B0ea96",
85+
"type": "sell",
86+
"transactions": [
87+
{
88+
"chainId": 466,
89+
"transactionHash": "0xc507bde1da0832d097c2160aacc2c9333ac3a0516c8dca4fb955f4c949da1ef6"
12390
},
124-
"status": "COMPLETED",
125-
"subStatus": "SUCCESS",
126-
"fromAddress": "0xebfb127320fcbe8e07e5a03a4bfb782219f4735b",
127-
"toAddress": "0xebfb127320fcbe8e07e5a03a4bfb782219f4735b",
128-
"quote": {
129-
"fromToken": {
130-
"chainId": 10,
131-
"tokenAddress": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
132-
"decimals": 18,
133-
"name": "ETH",
134-
"symbol": "ETH",
135-
"priceUSDCents": 346529
136-
},
137-
"toToken": {
138-
"chainId": 10,
139-
"tokenAddress": "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
140-
"decimals": 6,
141-
"name": "USD Coin",
142-
"symbol": "USDC",
143-
"priceUSDCents": 99
144-
},
145-
"fromAmountWei": "318486512146714",
146-
"fromAmount": "0.000318486512146714",
147-
"toAmountWei": "1100000",
148-
"toAmount": "1.1",
149-
"toAmountMinWei": "1100000",
150-
"toAmountMin": "1.1",
151-
"estimated": {
152-
"fromAmountUSDCents": 110,
153-
"toAmountMinUSDCents": 109,
154-
"toAmountUSDCents": 109,
155-
"slippageBPS": 91,
156-
"feesUSDCents": 32,
157-
"gasCostUSDCents": 40,
158-
"durationSeconds": 30
159-
},
160-
"createdAt": "2024-06-18T23:43:45.900Z"
161-
},
162-
"destination": {
163-
"transactionHash": "0x74d6c619a09e78f03f4bd495f29d5937a2539d0bbe8973e7710dce3e88c30b8b",
164-
"token": {
165-
"chainId": 10,
166-
"tokenAddress": "0x0b2c639c533813f4aa9d7837caf62653d097ff85",
167-
"decimals": 6,
168-
"name": "USD Coin",
169-
"symbol": "USDC",
170-
"priceUSDCents": 99
171-
},
172-
"amountWei": "1100000",
173-
"amount": "1.1",
174-
"amountUSDCents": 109,
175-
"completedAt": "2024-06-18T23:44:07.000Z"
91+
{
92+
"chainId": 8453,
93+
"transactionHash": "0xc44b372284061a11ec96c67acfc96a67cc6180d14753b55a93c1f780d16ddc95"
17694
}
177-
}
95+
],
96+
"developerFeeBps": 20,
97+
"developerFeeRecipient": "0x66d3d733e597bdf8b794ab6e13c8f2be0fcda39b"
17898
}
17999
}
180100
```
181101
</TabsContent>
182102
</Tabs>
183103

184-
## Webhook Verification (Recommended)
185-
186-
Since any outside origin can call your webhook endpoint, it is recommended to verify the webhook signature to ensure a request comes from your Pay instance.
187-
188-
### Check the Signature
189-
190-
The payload body is signed with the webhook secret and provided in the X-Pay-Signature request header.
191-
192-
Get the webhook secret for your webhook endpoint from the dashboard.
193-
194-
This code example checks if the signature is valid:
195-
196-
```tsx
197-
const generateSignature = (
198-
body: string,
199-
timestamp: string,
200-
secret: string,
201-
): string => {
202-
const payload = `${timestamp}.${body}`;
203-
return crypto.createHmac("sha256", secret).update(payload).digest("hex");
204-
};
205-
206-
const isValidSignature = (
207-
body: string,
208-
timestamp: string,
209-
signature: string,
210-
secret: string,
211-
): boolean => {
212-
const expectedSignature = generateSignature(body, timestamp, secret);
213-
return crypto.timingSafeEqual(
214-
Buffer.from(expectedSignature),
215-
Buffer.from(signature),
216-
);
217-
};
218-
```
104+
## Webhook Verification
219105

220-
Check the timestamp
221-
The event timestamp is provided in the X-Pay-Timestamp request header.
106+
There are two ways to verify a webhook request authenticity:
107+
1. Checking that the bearer token in the `Authorization` header matches the secret key received when you created the webhook.
108+
2. Decrypting the payload signature from the `x-payload-signature` header and verifying it against the received webhook body.
222109

223-
This code example checks if the event exceeds a given expiration duration:
110+
### Decrypting the Payload Signature
224111

225-
```tsx
226-
export const isExpired = (
227-
timestamp: string,
228-
expirationInSeconds: number,
229-
): boolean => {
230-
const currentTime = Math.floor(Date.now() / 1000);
231-
return currentTime - parseInt(timestamp) > expirationInSeconds;
232-
};
112+
The payload signature is constructed using the `x-timestamp` header and the webhook's full body:
113+
```ts
114+
const signature = crypto
115+
.createHmac("sha256", decryptedSecret)
116+
.update(`${timestamp}.${payload}`)
117+
.digest("hex");
233118
```
119+
The `x-timestamp` header is a UNIX timestamp in seconds, and the webhook body is the JSON payload received by your webhook endpoint.
234120

235-
### Example webhook endpoint
236-
237-
This NodeJS code example listens for event notifications on the /webhook endpoint:
238-
239-
```tsx
240-
import express from "express";
241-
import bodyParser from "body-parser";
242-
import { isValidSignature, isExpired } from "./webhookHelper";
243-
244-
const app = express();
245-
const port = 3000;
246-
247-
const WEBHOOK_SECRET = "<your_webhook_auth_secret>";
248-
249-
app.use(bodyParser.text());
250-
251-
app.post("/webhook", (req, res) => {
252-
const signatureFromHeader = req.header("X-Pay-Signature");
253-
const timestampFromHeader = req.header("X-Pay-Timestamp");
254-
255-
if (!signatureFromHeader || !timestampFromHeader) {
256-
return res.status(401).send("Missing signature or timestamp header");
257-
}
258-
259-
if (
260-
!isValidSignature(
261-
req.body,
262-
timestampFromHeader,
263-
signatureFromHeader,
264-
WEBHOOK_SECRET,
265-
)
266-
) {
267-
return res.status(401).send("Invalid signature");
268-
}
269-
270-
if (isExpired(timestampFromHeader, 300)) {
271-
// Assuming expiration time is 5 minutes (300 seconds)
272-
return res.status(401).send("Request has expired");
273-
}
274-
275-
// Process the request
276-
res.status(200).send("Webhook received!");
277-
});
278-
279-
app.listen(port, () => {
280-
console.log(`Server started on http://localhost:${port}`);
281-
});
282-
```
121+
Then verify that the signature matches the `x-payload-signature` header.

0 commit comments

Comments
 (0)