From ee64c8cf75b4a55049ef5551cb6bcb3d4894fe16 Mon Sep 17 00:00:00 2001 From: Eric Dahl Date: Wed, 23 Sep 2026 06:42:50 -0400 Subject: [PATCH 1/2] feat: add OpenClaw landing page --- app/openclaw/page.tsx | 239 +++++++++++++++++++++++++++++++++ app/page.tsx | 4 + tests/landing/copy.test.ts | 8 ++ tests/landing/openclaw.test.ts | 32 +++++ 4 files changed, 283 insertions(+) create mode 100644 app/openclaw/page.tsx create mode 100644 tests/landing/openclaw.test.ts diff --git a/app/openclaw/page.tsx b/app/openclaw/page.tsx new file mode 100644 index 0000000..0b0afec --- /dev/null +++ b/app/openclaw/page.tsx @@ -0,0 +1,239 @@ +import Link from 'next/link'; + +import { ArchitectureDiagram } from '@/components/app/architecture-diagram'; +import { Button } from '@/components/ui/button'; + +const openClawUseCases = [ + { + title: 'GitHub events reach your local automation', + description: + 'Point repositories at one public clawproxy route while OpenClaw runs privately on a lab box, workstation, or Coolify service.', + }, + { + title: 'Durable delivery for sleeping workers', + description: + 'clawproxy stores every accepted webhook until your OpenClaw node reconnects, pulls the event, and acknowledges it.', + }, + { + title: 'No inbound firewall changes', + description: + 'The OpenClaw node initiates an outbound WebSocket or pull connection, so you do not expose SSH, dashboards, or agent ports.', + }, +]; + +const setupSteps = [ + 'Create a node token in the clawproxy dashboard.', + 'Create a route for each provider or workflow.', + 'Configure OpenClaw with the relay URL and node token.', + 'Send GitHub, Stripe, and Slack webhooks to the generated route URL.', +]; + +const snippetLines = [ + 'OPENCLAW_WEBHOOK_RELAY_URL=wss://clawproxy.io/api/nodes/ws', + 'OPENCLAW_WEBHOOK_NODE_TOKEN=cpn_your_node_token', + 'OPENCLAW_WEBHOOK_ROUTE_URL=https://clawproxy.io/api/ingress/github-to-openclaw', +]; + +export const dynamic = 'force-dynamic'; + +export default function OpenClawLandingPage() { + return ( +
+
+
+ +
+
+ + + clawproxy + + + Webhook relay for OpenClaw workflows + + + + +
+ +
+
+

+ Built for private OpenClaw nodes +

+ +

+ Public webhooks for OpenClaw workflows. +

+ +

+ clawproxy is the webhook relay for OpenClaw workflows: accept provider events on the + public internet, queue them safely, and deliver them to a private OpenClaw node over an + outbound WebSocket or pull connection. +

+ +
+ + +
+
+ +
+
+
+
+
+
+

openclaw route

+

/api/ingress/github-to-openclaw

+
+

+ queued +

+
+
+ +
+
+

delivery path

+

+ Provider event in. OpenClaw action out. +

+

+ GitHub, Stripe, and Slack webhooks get a stable public endpoint while OpenClaw + keeps processing private behind NAT. +

+
+ +
+ {['Ingress accepted', 'Queued safely', 'OpenClaw acked'].map((label, index) => ( +
+

+ {String(index + 1).padStart(2, '0')} +

+

{label}

+
+ ))} +
+
+
+
+
+ +
+ {openClawUseCases.map((useCase, index) => ( +
+

{String(index + 1).padStart(2, '0')}

+

{useCase.title}

+

{useCase.description}

+
+ ))} +
+ +
+
+

+ Wire OpenClaw to clawproxy in minutes. +

+

+ Start with one route, verify delivery, then add more provider workflows without changing + how your OpenClaw node reaches the public internet. +

+
+ +
+
+

+ OpenClaw env +

+
+                {snippetLines.join('\n')}
+              
+
+ +
    + {setupSteps.map((step, index) => ( +
  1. + {String(index + 1).padStart(2, '0')} + {step} +
  2. + ))} +
+
+
+ +
+ +
+ +
+
+
+
+

+ Give OpenClaw a stable public webhook edge. +

+

+ Keep provider URLs stable, keep OpenClaw private, and let clawproxy handle the queue + between them. +

+

clawproxy.io/openclaw

+
+ +
+ + +
+
+
+
+
+ + +
+ ); +} diff --git a/app/page.tsx b/app/page.tsx index 74c8e19..ac2925e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -163,6 +163,10 @@ export default function Home() {

Works with Hermes Agent, OpenClaw, and any client that can receive events from an authenticated outbound connection. + {' '} + + Using OpenClaw? +

diff --git a/tests/landing/copy.test.ts b/tests/landing/copy.test.ts index 81fa440..2d4e956 100644 --- a/tests/landing/copy.test.ts +++ b/tests/landing/copy.test.ts @@ -23,4 +23,12 @@ describe('landing page copy', () => { expect(html).toContain('OpenClaw'); expect(html).not.toContain('private OpenClaw nodes'); }); + + test('includes a contextual entry point for OpenClaw users', async () => { + const html = await landingHtml(); + + expect(html).toContain('href="/openclaw"'); + expect(html).toContain('Using OpenClaw?'); + }); + }); diff --git a/tests/landing/openclaw.test.ts b/tests/landing/openclaw.test.ts new file mode 100644 index 0000000..a98ac12 --- /dev/null +++ b/tests/landing/openclaw.test.ts @@ -0,0 +1,32 @@ +import { JSDOM } from 'jsdom'; +import { describe, expect, test } from 'vitest'; +import { renderToStaticMarkup } from 'react-dom/server'; + +async function openClawLandingHtml() { + const page = await import('@/app/openclaw/page'); + return renderToStaticMarkup(page.default()); +} + +describe('OpenClaw landing page', () => { + test('targets OpenClaw users with a clear hero and dashboard CTA', async () => { + const html = await openClawLandingHtml(); + const dom = new JSDOM(html); + const document = dom.window.document; + + expect(document.querySelector('h1')?.textContent).toContain('OpenClaw workflows'); + expect(html).toContain('webhook relay for OpenClaw workflows'); + const dashboardLinks = Array.from(document.querySelectorAll('a[href="/dashboard"]')); + + expect(dashboardLinks.some((link) => link.textContent?.trim() === 'Open dashboard')).toBe(true); + }); + + test('shows the OpenClaw setup snippet and delivery path', async () => { + const html = await openClawLandingHtml(); + + expect(html).toContain('OPENCLAW_WEBHOOK_RELAY_URL'); + expect(html).toContain('wss://clawproxy.io/api/nodes/ws'); + expect(html).toContain('GitHub, Stripe, and Slack webhooks'); + expect(html).toContain('OpenClaw node'); + expect(html).toContain('outbound WebSocket'); + }); +}); From 7503d196ce4b536d07daf25cd895c70c3acb5414 Mon Sep 17 00:00:00 2001 From: Eric Dahl Date: Wed, 23 Sep 2026 06:49:58 -0400 Subject: [PATCH 2/2] fix: correct OpenClaw ingress URL copy --- app/openclaw/page.tsx | 11 +++++++---- tests/landing/openclaw.test.ts | 4 ++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/openclaw/page.tsx b/app/openclaw/page.tsx index 0b0afec..ea6f6ba 100644 --- a/app/openclaw/page.tsx +++ b/app/openclaw/page.tsx @@ -31,7 +31,7 @@ const setupSteps = [ const snippetLines = [ 'OPENCLAW_WEBHOOK_RELAY_URL=wss://clawproxy.io/api/nodes/ws', 'OPENCLAW_WEBHOOK_NODE_TOKEN=cpn_your_node_token', - 'OPENCLAW_WEBHOOK_ROUTE_URL=https://clawproxy.io/api/ingress/github-to-openclaw', + 'OPENCLAW_WEBHOOK_ROUTE_URL=https://clawproxy.io/api/ingress//github-to-openclaw', ]; export const dynamic = 'force-dynamic'; @@ -113,7 +113,9 @@ export default function OpenClawLandingPage() {

openclaw route

-

/api/ingress/github-to-openclaw

+

+ /api/ingress/<user-id>/github-to-openclaw +

queued @@ -164,8 +166,9 @@ export default function OpenClawLandingPage() { Wire OpenClaw to clawproxy in minutes.

- Start with one route, verify delivery, then add more provider workflows without changing - how your OpenClaw node reaches the public internet. + Start with one dashboard-generated route URL that includes your user id, verify delivery, + then add more provider workflows without changing how your OpenClaw node reaches the + public internet.

diff --git a/tests/landing/openclaw.test.ts b/tests/landing/openclaw.test.ts index a98ac12..f954916 100644 --- a/tests/landing/openclaw.test.ts +++ b/tests/landing/openclaw.test.ts @@ -25,6 +25,10 @@ describe('OpenClaw landing page', () => { expect(html).toContain('OPENCLAW_WEBHOOK_RELAY_URL'); expect(html).toContain('wss://clawproxy.io/api/nodes/ws'); + expect(html).toContain('https://clawproxy.io/api/ingress/<user-id>/github-to-openclaw'); + expect(html).toContain('/api/ingress/<user-id>/github-to-openclaw'); + expect(html).toContain('dashboard-generated route URL that includes your user id'); + expect(html).not.toContain('https://clawproxy.io/api/ingress/github-to-openclaw'); expect(html).toContain('GitHub, Stripe, and Slack webhooks'); expect(html).toContain('OpenClaw node'); expect(html).toContain('outbound WebSocket');