{"id":51178412,"url":"https://github.com/beaufour/openclaw-api-server","last_synced_at":"2026-06-27T05:30:43.426Z","repository":{"id":365911380,"uuid":"1193193168","full_name":"beaufour/openclaw-api-server","owner":"beaufour","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-19T11:53:13.000Z","size":221,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T13:25:09.592Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/beaufour.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-27T01:03:46.000Z","updated_at":"2026-06-19T11:51:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/beaufour/openclaw-api-server","commit_stats":null,"previous_names":["beaufour/openclaw-api-server"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/beaufour/openclaw-api-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beaufour%2Fopenclaw-api-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beaufour%2Fopenclaw-api-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beaufour%2Fopenclaw-api-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beaufour%2Fopenclaw-api-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/beaufour","download_url":"https://codeload.github.com/beaufour/openclaw-api-server/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/beaufour%2Fopenclaw-api-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34843146,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-27T05:30:42.708Z","updated_at":"2026-06-27T05:30:43.417Z","avatar_url":"https://github.com/beaufour.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OpenClaw Webhook Receiver\n\n[OpenClaw](https://openclaw.ai/) webhook receiver for external APIs. Replaces expensive LLM-monitored polling with near-real-time webhooks.\n\n## Architecture\n\n```\nGmail (Pub/Sub Push) ──┐\nAsana (Webhooks)    ───┼──→ Cloudflare Edge ──→ cloudflared tunnel ──→ webhook server (:18790) ──→ OpenClaw Gateway (:18789)\nStrava (Webhooks)   ───┘                                                  scripts/server.ts          POST /hooks/\u003csource\u003e\n```\n\nIt runs as a small standalone HTTP server (`scripts/server.ts`): it terminates the public webhook traffic, validates each provider's auth, parses the payload, and forwards a wake event to the OpenClaw Gateway's `/hooks/\u003csource\u003e` endpoint. The Gateway and the tunnel are separate processes.\n\n\u003e There is also an in-process Gateway plugin entry point (`src/index.ts`) using the same handlers, but the supported / actually-deployed setup is the standalone server described here.\n\n## Supported Services\n\n| Service | Endpoint | Method | Auth |\n|---------|----------|--------|------|\n| Gmail | `/webhook/gmail` | POST | OIDC JWT from Pub/Sub + optional DKIM/allowlist |\n| Asana | `/webhook/asana` | POST | HMAC-SHA256 signature |\n| Strava | `/webhook/strava/:secret` | GET/POST | Secret URL path segment |\n| (health) | `/health` | GET | none |\n\n## Running it\n\nThree processes need to be up: the OpenClaw Gateway (on `:18789`), this webhook server, and the cloudflared tunnel.\n\n```bash\nnpm install\nnpm start                      # = npx tsx scripts/server.ts — listens on :18790, forwards to the Gateway\nnpm run dev                    # same, with --watch and --dry-run (logs events, does not forward)\n\n# extra flags\nnpx tsx scripts/server.ts --dry-run        # log events instead of forwarding\nnpx tsx scripts/server.ts --log-payload    # also log the full forwarded payload\n```\n\nSanity checks once everything is up:\n\n```bash\ncurl localhost:18790/health            # {\"status\":\"ok\"}\ncurl https://yugle.yigle.us/health     # same, through the tunnel\n```\n\n## Configuration\n\nConfig is read from a `.env` file in the repo root (or real environment variables — see `.env.example`).\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `PORT` | Port the webhook server listens on | `18790` |\n| `OPENCLAW_GATEWAY_URL` | Base URL of the OpenClaw Gateway to forward events to | `http://localhost:18789` |\n| `OPENCLAW_HOOK_TOKEN` | Bearer token sent to the Gateway's `/hooks/\u003csource\u003e` endpoints | (empty — forwarding will fail) |\n| `ASANA_WEBHOOK_SECRET` | HMAC secret for Asana signature validation | (auto-persisted from handshake) |\n| `STRAVA_VERIFY_TOKEN` | Token for Strava subscription validation | (empty) |\n| `STRAVA_WEBHOOK_SECRET` | Secret path segment in the Strava callback URL | (empty) |\n| `GMAIL_PUBSUB_AUDIENCE` | Audience for Pub/Sub OIDC JWT validation | (empty, skips auth) |\n| `GMAIL_REQUIRE_DKIM` | Set to `true` to verify DKIM and check the sender allowlist | `false` |\n| `DATA_DIR` | Directory for persisted state (Asana secrets, allowlist) | `~/.openclaw-api-server` |\n\n## Cloudflare Tunnel\n\nExpose the webhook server to the internet without opening ports:\n\n```bash\nbrew install cloudflared\ncloudflared login\ncloudflared tunnel create openclaw\ncloudflared tunnel route dns openclaw webhooks.yourdomain.com\n```\n\nCreate `~/.cloudflared/config.yml`:\n\n```yaml\ntunnel: openclaw\ncredentials-file: ~/.cloudflared/\u003ctunnel-id\u003e.json\n\ningress:\n  - hostname: webhooks.yourdomain.com\n    service: http://localhost:18790\n  - service: http_status:404\n```\n\n```bash\ncloudflared tunnel run openclaw\n```\n\n## Registering Webhooks\n\n### Gmail (Pub/Sub Push)\n\n1. Enable Gmail API + Pub/Sub API in Google Cloud Console\n2. Create a Pub/Sub topic and grant publish permission to `gmail-api-push@system.gserviceaccount.com`\n3. Create a push subscription pointing to `https://webhooks.yourdomain.com/webhook/gmail`\n4. Set `GMAIL_PUBSUB_AUDIENCE` to the same URL for JWT validation\n5. Call the Gmail API `watch()` method (must be renewed every 7 days)\n\nThere's a helper for steps 1–5: `scripts/setup-gmail.sh`.\n\n#### DKIM Verification \u0026 Sender Allowlist (optional)\n\nSet `GMAIL_REQUIRE_DKIM=true` to verify that incoming emails pass DKIM. Gmail already verifies DKIM on receipt — this server reads the `Authentication-Results` header via the Gmail API to check the result.\n\nTo restrict which senders can trigger agent actions, create `DATA_DIR/gmail_sender_allowlist.json`:\n\n```json\n[\n  { \"fromEmail\": \"boss@company.com\", \"dkimDomain\": \"company.com\" },\n  { \"fromEmail\": \"alerts@monitoring.io\", \"dkimDomain\": \"monitoring.io\" }\n]\n```\n\nBoth the From email **and** the DKIM signing domain must match an entry. If the allowlist file is empty or missing, all DKIM-passing emails are allowed through.\n\n### Asana\n\n1. Create a webhook via the Asana API with callback URL `https://webhooks.yourdomain.com/webhook/asana` (see `scripts/setup-asana.sh`)\n2. The server handles the handshake automatically and persists the secret to `DATA_DIR`\n3. Optionally set `ASANA_WEBHOOK_SECRET` to override the persisted secret\n\n### Strava\n\n1. Set `STRAVA_VERIFY_TOKEN` and `STRAVA_WEBHOOK_SECRET` in `.env`\n2. Register a webhook subscription via the Strava API (see `scripts/setup-strava.sh`)\n3. Callback URL: `https://webhooks.yourdomain.com/webhook/strava/\u003cSTRAVA_WEBHOOK_SECRET\u003e`\n\n## Development\n\n```bash\nnpm install\nnpm run check          # Lint + type check + tests\n\n# Individual commands\nnpx biome check src/ tests/    # Lint\nnpx biome check --fix src/     # Auto-fix\nnpx tsc --noEmit               # Type check\nnpx vitest run                 # Tests\nnpx vitest                     # Tests in watch mode\n```\n\nPre-commit hook (biome + tsc + vitest):\n```bash\ngit config core.hooksPath .githooks\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeaufour%2Fopenclaw-api-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbeaufour%2Fopenclaw-api-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbeaufour%2Fopenclaw-api-server/lists"}