diff --git a/apps/portal/src/app/bridge/webhooks/assets/bridge-webhooks.png b/apps/portal/src/app/bridge/webhooks/assets/bridge-webhooks.png new file mode 100644 index 00000000000..956787f7a61 Binary files /dev/null and b/apps/portal/src/app/bridge/webhooks/assets/bridge-webhooks.png differ diff --git a/apps/portal/src/app/bridge/webhooks/page.mdx b/apps/portal/src/app/bridge/webhooks/page.mdx index 6f2e1c71bb3..f56c55fe978 100644 --- a/apps/portal/src/app/bridge/webhooks/page.mdx +++ b/apps/portal/src/app/bridge/webhooks/page.mdx @@ -2,6 +2,7 @@ import { Callout, OpenSourceCard, createMetadata, + DocImage, InstallTabs, SDKCard, Grid, @@ -19,25 +20,31 @@ import { UnrealEngineIcon, } from "@/icons"; +import BridgeWebhookScreen from "./assets/bridge-webhooks.png"; + export const metadata = createMetadata({ image: { - title: "Payments Webhooks", + title: "Bridge Webhooks", icon: "payments", }, - title: "Payments Webhooks", - description: "Learn how to set up and handle webhooks for thirdweb Payments transactions.", + title: "Bridge Webhooks", + description: "Learn how to set up and handle webhooks for thirdweb Bridge transactions.", }); -# Webhooks +# Bridge Webhooks + +Create webhooks to be notified on each bridge, swap, and onramp transaction. -Create webhooks to be notified each time a payment is completed in your app. +## Dashboard -## Create a Webhook +You can create a webhook in your project dashboard by navigating to Bridge > Webhooks tab. -You can create a webhook in your project dashboard under the Webhooks > 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. +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. + -## Parse Webhook Payload with thirdweb SDK + +## Parse Webhook Payload The thirdweb SDK has a built-in function for parsing bridge webhook payloads @@ -103,7 +110,6 @@ const payload = await Bridge.Webhook.parse( ``` - ## Example Payloads diff --git a/apps/portal/src/app/tokens/sidebar.tsx b/apps/portal/src/app/tokens/sidebar.tsx index d62645ba26c..1dd8d9f33e3 100644 --- a/apps/portal/src/app/tokens/sidebar.tsx +++ b/apps/portal/src/app/tokens/sidebar.tsx @@ -37,6 +37,10 @@ export const sidebar: SideBar = { ], name: "Deploy Tokens", }, + { + href: `${slug}/webhooks`, + name: "Webhooks", + }, ], name: "Guides", }, diff --git a/apps/portal/src/app/tokens/webhooks/assets/token-webhooks.png b/apps/portal/src/app/tokens/webhooks/assets/token-webhooks.png new file mode 100644 index 00000000000..b6d7ef16ff5 Binary files /dev/null and b/apps/portal/src/app/tokens/webhooks/assets/token-webhooks.png differ diff --git a/apps/portal/src/app/tokens/webhooks/page.mdx b/apps/portal/src/app/tokens/webhooks/page.mdx new file mode 100644 index 00000000000..825deecad99 --- /dev/null +++ b/apps/portal/src/app/tokens/webhooks/page.mdx @@ -0,0 +1,30 @@ +import { DocImage} from "@doc" +import TokenWebhooksScreen from "./assets/token-webhooks.png" + +# Webhooks + +You can create webhooks to receive notifications for contract events such as token transfers or state changes or for blockchain transactions on any contract. + +With webhooks, you'll receive event data in real-time without making constant requests, reducing RPC load and the risk of missing events. + +## Use Cases + +Your app may trigger an action when an onchain event occurs, such as: + +- ETH or an ERC20 currency is transferred to or from a wallet. +- A token is minted from your NFT collection. +- A token in your NFT collection is burned or transferred. +- Metadata for an oracle contract is updated. + +## Create Webhooks + +You can configure token webhooks from the dashboard by navigating to your project and selecting Tokens > Webhooks + + + + + + + + + diff --git a/apps/portal/src/app/wallets/security/page.mdx b/apps/portal/src/app/wallets/security/page.mdx index 147cffed684..811ead22a37 100644 --- a/apps/portal/src/app/wallets/security/page.mdx +++ b/apps/portal/src/app/wallets/security/page.mdx @@ -196,7 +196,7 @@ The response also arrives in the same payload format. We just perform the steps ## Wallets Audits -View the full audit reports for any wallets related features and specs: +View the full audit reports for any wallets-related features and specs: - [7702 (Minimal Account)](https://0xmacro.com/library/audits/thirdweb-22) - [Managed Account Factory](https://ipfs.io/ipfs/Qmc36VUCuwG2u7kZrqmXmJsH5c8sF7SHySVbPnwVmo3XYX/thirdweb%20A-14%20_%20Macro%20Audits%20_%20The%200xMacro%20Library.pdf) diff --git a/apps/portal/src/app/webhooks/layout.tsx b/apps/portal/src/app/webhooks/layout.tsx deleted file mode 100644 index 8acb56172d8..00000000000 --- a/apps/portal/src/app/webhooks/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { createMetadata } from "@/components/Document"; -import { DocLayout } from "@/components/Layouts/DocLayout"; -import { sidebar } from "./sidebar"; -export default async function Layout(props: { children: React.ReactNode }) { - return ( - - {props.children} - - ); -} - -export const metadata = createMetadata({ - description: "Receive real-time updates for onchain and offchain events.", - image: { - icon: "webhooks", - title: "Thirdweb Webhooks", - }, - title: "thirdweb Webhooks", -}); diff --git a/apps/portal/src/app/webhooks/page.mdx b/apps/portal/src/app/webhooks/page.mdx deleted file mode 100644 index 1060567515d..00000000000 --- a/apps/portal/src/app/webhooks/page.mdx +++ /dev/null @@ -1,160 +0,0 @@ -import { createMetadata, Details } from "@/components/Document"; -import { DocImage, FeatureCard, OpenSourceCard, Callout } from "@doc"; -import { ArrowLeftRightIcon, UserLockIcon, UsersIcon, WalletIcon } from "lucide-react"; - - -export const metadata = createMetadata({ - title: "thirdweb Webhooks", - description: "Receive real-time updates for onchain and offchain events.", -}); - -# Webhooks - -Webhooks are a way to receive real-time updates for onchain and offchain events. - -An **event** is a real-time update or action that occured for your team or project. - -A **webhook** is a configured endpoint on your backend that receives events. - -A **topic** is an identifier that groups events (example: `engine.transaction.sent`). -- The thirdweb product ("engine") -- The object ("engine.transaction") -- The event that occurred ("sent") - -Each webhook can subscribe to multiple topics. - -## Quickstart - -1. Configure an HTTP endpoint on your backend to receive events. -1. Create a webhook in the thirdweb dashboard to subscribe to topics. -1. (Recommended) Verify the webhook signature to secure your endpoint. - -## Manage your webhooks - -Manage your webhooks from the thirdweb dashboard. - -1. Select your team and project. -1. Select **Webhooks** in the left sidebar. - -### Create a webhook - -Select **Create Webhook** to create a new webhook. - -Provide the following details: -- Description: A name for your webhook. -- Destination URL: The URL to send the webhook to. Only HTTPS URLs are supported. -- Topics: The thirdweb topics to subscribe this webhook to. -- Start Paused: Whether the webhook should immediately start receiving events. - -### Update or delete a webhook - -Find your webhook in the list. -- Select **... > Edit** to update your webhook details or subscribed topics. -- Select **... > Delete** to delete it. - -### View analytics - -Monitor your webhooks' requests over time to identify errors or latency issues. - -## Retries - -Webhook delivery attempts only consider a **2xx status code** returned within the **10-second timeout** as successful. - -Otherwise the delivery will retry multiple times over the next 24 hours with exponential backoff. - -### Automatic suspension of failing webhooks - -Webhooks experiencing high error rates (non-2xx status codes) sustained over several hours will be paused automatically. -A paused webhook cannot receive any webhook events until it is manually resumed from the dashboard. - -You will be notified via email and a dashboard notification when your webhook is paused. - -## Handle webhook events - -Your HTTP endpoint should handle webhook events and return a 200 status code quickly. Avoid slow or long-running synchronous operations, or move them to a queue in your backend. - -### HTTP format - -Webhooks are sent as a `POST` request to your configured endpoint. - -**Headers** - -- `content-type`: `application/json` -- `x-webhook-id`: A unique identifier for this webhook -- `x-webhook-signature`: HMAC-SHA256 signature - - See *Verify webhook signature* below -- `x-webhook-timestamp`: Timestamp of delivery attempt in Unix seconds - - See *Reject expired webhooks* below - -**Request body** - -```json -{ - "id": "evt_cllcqqie908ii4q0ugld6noeu", - "type": "engine.transaction.sent", - "triggered_at": 1752471197, - "object": "engine.transaction", - "data": { - // ...engine.transaction fields - }, -} -``` - -- `id`: A unique identifier for the event for this topic. Multiple delivery attempts for the same event will use the same ID. -- `type`: The topic that an event was triggered for. -- `triggered_at`: The timestamp the event was triggered in Unix seconds. - - Note: This value does not change for each delivery attempt, but the `x-webhook-timestamp` header does. -- `object`: The object that defines the shape of `data`. -- `data`: The object payload for the event. - -### Secure your webhook endpoint - -The `x-webhook-signature` header is a signature that hashes the raw request body and the delivery timestamp. -This signature ensures that neither the request body nor the timestamp were modified and can be trusted as sent from thirdweb. - -Follow these steps to verify the webhook signature: -1. Concatenate `{TIMESTAMP_IN_UNIX_SECONDS}.{REQUEST_JSON_BODY}`. -1. Hash the result with the webhook secret using SHA256. -1. Compare the result with the `x-webhook-signature` header. - -**Code examples** - -
-```typescript -import { createHmac, timingSafeEqual } from "crypto"; - -const webhookSecret = "whsecret_..."; // Your webhook secret from the dashboard -const actualSignature = req.headers["x-webhook-signature"]; -const timestamp = req.headers["x-webhook-timestamp"]; -const body = "..." // raw HTTP body as string - -// Generate the expected signature. -const expectedSignature = createHmac("sha256", webhookSecret) - .update(`${timestamp}.${body}`) - .digest("hex"); - -// Use `timingSafeEqual` to compare the signatures safely. -const expected = Buffer.from(expectedSignature, "hex"); -const actual = Buffer.from(actualSignature, "hex"); -const isValidSignature = - expected.length === actual.length && - timingSafeEqual(expected, actual); - -if (!isValidSignature) { - throw new Error("Invalid webhook signature"); -} -``` -
- -### Reject expired webhooks (optional) - -You can reject webhook attempts that are received after a certain duration. This prevents requests from being replayed. - -```typescript -const MAX_AGE_SECONDS = 10 * 60 * 1000; // 10 minutes -const timestamp = req.headers["x-webhook-timestamp"]; -if (Date.now() / 1000 - timestamp > MAX_AGE_SECONDS) { - throw new Error("Webhook expired"); -} -``` - diff --git a/apps/portal/src/app/webhooks/sidebar.tsx b/apps/portal/src/app/webhooks/sidebar.tsx deleted file mode 100644 index 56bcdb2a51b..00000000000 --- a/apps/portal/src/app/webhooks/sidebar.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { WebhookIcon } from "lucide-react"; -import type { SideBar } from "@/components/Layouts/DocLayout"; - -export const sidebar: SideBar = { - links: [ - { - href: "/webhooks", - icon: , - name: "Overview", - }, - ], - name: "Webhooks", -};