Skip to content

Commit 6bf6108

Browse files
committed
chore: webhooks docs (#7604)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR introduces a new `Webhooks` section in the application, providing a sidebar navigation item, a dedicated layout, and detailed documentation on webhooks, including their configuration, management, and security practices. ### Detailed summary - Added `sidebar` configuration for `Webhooks` in `sidebar.tsx`. - Introduced `SDKCard` for `Webhooks` in `page.tsx`. - Renamed `BAST_URL` to `BASE_URL` in `metadata.ts`. - Created a new `Layout` component for `Webhooks` in `layout.tsx`. - Defined metadata for `Webhooks` in `layout.tsx`. - Added comprehensive documentation for webhooks in `page.mdx`, covering setup, management, event handling, and security. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced a new "Webhooks" section in the backend APIs grid, accessible from the main portal. * Added comprehensive documentation for thirdweb Webhooks, including setup, management, security, and troubleshooting guides. * Implemented a dedicated sidebar and layout for the Webhooks documentation page for easier navigation. * **Documentation** * Provided detailed instructions and code examples for implementing and verifying webhook integrations. * **Bug Fixes** * Corrected a typo in the base URL constant used for metadata generation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent ce85be3 commit 6bf6108

File tree

5 files changed

+211
-4
lines changed

5 files changed

+211
-4
lines changed

apps/portal/src/app/page.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MessageCircleIcon } from "lucide-react";
1+
import { MessageCircleIcon, WebhookIcon } from "lucide-react";
22
import Image from "next/image";
33
import Link from "next/link";
44
import { Grid, Heading, SDKCard } from "@/components/Document";
@@ -147,6 +147,12 @@ function ReferenceSection() {
147147
iconClassName="text-muted-foreground"
148148
title="Bundler"
149149
/>
150+
<SDKCard
151+
href="/webhooks"
152+
icon={WebhookIcon}
153+
iconClassName="text-muted-foreground"
154+
title="Webhooks"
155+
/>
150156
</Grid>
151157
</section>
152158
);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { createMetadata } from "@/components/Document";
2+
import { DocLayout } from "@/components/Layouts/DocLayout";
3+
import { sidebar } from "./sidebar";
4+
export default async function Layout(props: { children: React.ReactNode }) {
5+
return (
6+
<DocLayout
7+
editPageButton={true}
8+
sideBar={sidebar}
9+
sidebarHeader={
10+
<div className="flex-col items-center gap-1">
11+
<p className="py-5 font-semibold text-foreground text-lg">Webhooks</p>
12+
</div>
13+
}
14+
>
15+
{props.children}
16+
</DocLayout>
17+
);
18+
}
19+
20+
export const metadata = createMetadata({
21+
description: "Receive real-time updates for onchain and offchain events.",
22+
image: {
23+
icon: "webhooks",
24+
title: "Thirdweb Webhooks",
25+
},
26+
title: "thirdweb Webhooks",
27+
});
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { createMetadata, Details } from "@/components/Document";
2+
import { DocImage, FeatureCard, OpenSourceCard, Callout } from "@doc";
3+
import { ArrowLeftRightIcon, UserLockIcon, UsersIcon, WalletIcon } from "lucide-react";
4+
5+
6+
export const metadata = createMetadata({
7+
title: "thirdweb Webhooks",
8+
description: "Receive real-time updates for onchain and offchain events.",
9+
});
10+
11+
# Webhooks
12+
13+
Webhooks are a way to receive real-time updates for onchain and offchain events.
14+
15+
An **event** is a real-time update or action that occured for your team or project.
16+
17+
A **webhook** is a configured endpoint on your backend that receives events.
18+
19+
A **topic** is an identifier that groups events (example: `engine.transaction.sent`).
20+
- The thirdweb product ("engine")
21+
- The object ("engine.transaction")
22+
- The event that occurred ("sent")
23+
24+
Each webhook can subscribe to multiple topics.
25+
26+
## Quickstart
27+
28+
1. Configure an HTTP endpoint on your backend to receive events.
29+
1. Create a webhook in the thirdweb dashboard to subscribe to topics.
30+
1. (Recommended) Verify the webhook signature to secure your endpoint.
31+
32+
## Manage your webhooks
33+
34+
Manage your webhooks from the thirdweb dashboard.
35+
36+
1. Select your team and project.
37+
1. Select **Webhooks** in the left sidebar.
38+
39+
### Create a webhook
40+
41+
Select **Create Webhook** to create a new webhook.
42+
43+
Provide the following details:
44+
- Description: A name for your webhook.
45+
- Destination URL: The URL to send the webhook to. Only HTTPS URLs are supported.
46+
- Topics: The thirdweb topics to subscribe this webhook to.
47+
- Start Paused: Whether the webhook should immediately start receiving events.
48+
49+
### Update or delete a webhook
50+
51+
Find your webhook in the list.
52+
- Select **... > Edit** to update your webhook details or subscribed topics.
53+
- Select **... > Delete** to delete it.
54+
55+
### View analytics
56+
57+
Monitor your webhooks' requests over time to identify errors or latency issues.
58+
59+
## Retries
60+
61+
Webhook delivery attempts only consider a **2xx status code** returned within the **10-second timeout** as successful.
62+
63+
Otherwise the delivery will retry multiple times over the next 24 hours with exponential backoff.
64+
65+
### Automatic suspension of failing webhooks
66+
67+
Webhooks experiencing high error rates (non-2xx status codes) sustained over several hours will be paused automatically.
68+
A paused webhook cannot receive any webhook events until it is manually resumed from the dashboard.
69+
70+
You will be notified via email and a dashboard notification when your webhook is paused.
71+
72+
## Handle webhook events
73+
74+
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.
75+
76+
### HTTP format
77+
78+
Webhooks are sent as a `POST` request to your configured endpoint.
79+
80+
**Headers**
81+
82+
- `content-type`: `application/json`
83+
- `x-webhook-id`: A unique identifier for this webhook
84+
- `x-webhook-signature`: HMAC-SHA256 signature
85+
- See *Verify webhook signature* below
86+
- `x-webhook-timestamp`: Timestamp of delivery attempt in Unix seconds
87+
- See *Reject expired webhooks* below
88+
89+
**Request body**
90+
91+
```json
92+
{
93+
"id": "evt_cllcqqie908ii4q0ugld6noeu",
94+
"type": "engine.transaction.sent",
95+
"triggered_at": 1752471197,
96+
"object": "engine.transaction",
97+
"data": {
98+
// ...engine.transaction fields
99+
},
100+
}
101+
```
102+
103+
- `id`: A unique identifier for the event for this topic. Multiple delivery attempts for the same event will use the same ID.
104+
- `type`: The topic that an event was triggered for.
105+
- `triggered_at`: The timestamp the event was triggered in Unix seconds.
106+
- Note: This value does not change for each delivery attempt, but the `x-webhook-timestamp` header does.
107+
- `object`: The object that defines the shape of `data`.
108+
- `data`: The object payload for the event.
109+
110+
### Secure your webhook endpoint
111+
112+
The `x-webhook-signature` header is a signature that hashes the raw request body and the delivery timestamp.
113+
This signature ensures that neither the request body nor the timestamp were modified and can be trusted as sent from thirdweb.
114+
115+
Follow these steps to verify the webhook signature:
116+
1. Concatenate `{TIMESTAMP_IN_UNIX_SECONDS}.{REQUEST_JSON_BODY}`.
117+
1. Hash the result with the webhook secret using SHA256.
118+
1. Compare the result with the `x-webhook-signature` header.
119+
120+
**Code examples**
121+
122+
<Details summary="Verify webhook signature in TypeScript">
123+
```typescript
124+
import { createHmac, timingSafeEqual } from "crypto";
125+
126+
const webhookSecret = "whsecret_..."; // Your webhook secret from the dashboard
127+
const actualSignature = req.headers["x-webhook-signature"];
128+
const timestamp = req.headers["x-webhook-timestamp"];
129+
const body = "..." // raw HTTP body as string
130+
131+
// Generate the expected signature.
132+
const expectedSignature = createHmac("sha256", webhookSecret)
133+
.update(`${timestamp}.${body}`)
134+
.digest("hex");
135+
136+
// Use `timingSafeEqual` to compare the signatures safely.
137+
const expected = Buffer.from(expectedSignature, "hex");
138+
const actual = Buffer.from(actualSignature, "hex");
139+
const isValidSignature =
140+
expected.length === actual.length &&
141+
timingSafeEqual(expected, actual);
142+
143+
if (!isValidSignature) {
144+
throw new Error("Invalid webhook signature");
145+
}
146+
```
147+
</Details>
148+
149+
### Reject expired webhooks (optional)
150+
151+
You can reject webhook attempts that are received after a certain duration. This prevents requests from being replayed.
152+
153+
```typescript
154+
const MAX_AGE_SECONDS = 10 * 60 * 1000; // 10 minutes
155+
const timestamp = req.headers["x-webhook-timestamp"];
156+
if (Date.now() / 1000 - timestamp > MAX_AGE_SECONDS) {
157+
throw new Error("Webhook expired");
158+
}
159+
```
160+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { WebhookIcon } from "lucide-react";
2+
import type { SideBar } from "@/components/Layouts/DocLayout";
3+
4+
export const sidebar: SideBar = {
5+
links: [
6+
{
7+
href: "/webhooks",
8+
icon: <WebhookIcon />,
9+
name: "Overview",
10+
},
11+
],
12+
name: "Webhooks",
13+
};

apps/portal/src/components/Document/metadata.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Metadata } from "next";
22
import { getBaseUrl } from "../../lib/getBaseUrl";
33

4-
const BAST_URL = getBaseUrl();
4+
const BASE_URL = getBaseUrl();
55

66
type DynamicImageOptions = {
77
title: string;
@@ -22,7 +22,8 @@ type DynamicImageOptions = {
2222
| "dotnet"
2323
| "nebula"
2424
| "unreal-engine"
25-
| "insight";
25+
| "insight"
26+
| "webhooks";
2627
};
2728

2829
export type MetadataImageIcon = DynamicImageOptions["icon"];
@@ -41,7 +42,7 @@ export function createMetadata(obj: {
4142
? [
4243
{
4344
height: 630,
44-
url: `${BAST_URL}/api/og?icon=${obj.image.icon}&title=${obj.image.title}`,
45+
url: `${BASE_URL}/api/og?icon=${obj.image.icon}&title=${obj.image.title}`,
4546
width: 1200,
4647
},
4748
]

0 commit comments

Comments
 (0)