diff --git a/app/openclaw/page.tsx b/app/openclaw/page.tsx new file mode 100644 index 0000000..ea6f6ba --- /dev/null +++ b/app/openclaw/page.tsx @@ -0,0 +1,242 @@ +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/<user-id>/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 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. +

+
+ +
+
+

+ 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..f954916 --- /dev/null +++ b/tests/landing/openclaw.test.ts @@ -0,0 +1,36 @@ +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('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'); + }); +});