feedhook · docs

Turn a YouTube channel into a webhook

Register a channel and a callback URL; your endpoint gets a signed JSON POST ~8 seconds after every new video. YouTube's own WebSub push underneath — no polling, no API quota, no cron. Free for 1 feed; Pro $9/mo for 10.

Quickstart

# 1. Sign up — the API key is shown once
curl -X POST https://feedhook.walls.sh/accounts -d '{"email":"you@example.com"}'

# 2. Turn a channel into a webhook (@handle, channel URL, or UC… id)
curl -X POST https://feedhook.walls.sh/subscriptions \
  -H 'authorization: Bearer fh_your_key' \
  -d '{"channel":"@mkbhd","callbackUrl":"https://your.app/hook"}'
# → save the returned "secret" — it signs every delivery and is shown once

# 3. Verify your receiver right now (no need to wait for a video)
curl -X POST https://feedhook.walls.sh/subscriptions/SUB_ID/test \
  -H 'authorization: Bearer fh_your_key'

The webhook you receive

POST https://your.app/hook
content-type: application/json
x-feedhook-event: video.published
x-feedhook-delivery: <uuid>
x-feedhook-signature: sha256=<hex>

{
  "event": "video.published",
  "subscriptionId": "…",
  "videoId": "dQw4w9WgXcQ",
  "channelId": "UC…",
  "title": "…",
  "author": "…",
  "publishedAt": "2026-06-11T15:54:18+00:00",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "receivedAt": "…"
}

Respond with any 2xx within 15 seconds. Verify the signature — HMAC-SHA256 of the raw body, keyed with your subscription's secret:

// node
import { createHmac, timingSafeEqual } from "node:crypto";
const expected = "sha256=" + createHmac("sha256", SECRET).update(rawBody).digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-feedhook-signature"] || ""));
# python
import hmac, hashlib
expected = "sha256=" + hmac.new(SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, request.headers.get("x-feedhook-signature", ""))
# ruby (rails)
expected = "sha256=" + OpenSSL::HMAC.hexdigest("sha256", secret, request.raw_post)
ok = ActiveSupport::SecurityUtils.secure_compare(expected, request.headers["x-feedhook-signature"].to_s)

Reliability — never loses a video

We don't claim "never fails"; nobody can. We claim no silent loss, with three mechanisms you can audit:

If…then…
your endpoint is down8 retries over ~9 hours, every attempt persisted to disk and visible in your delivery log — retries survive our own restarts. Down longer? Failed payloads are kept: one call redelivers them
we miss a push (downtime, hub hiccup)an hourly reconciliation sweep polls each channel's feed and delivers what push missed — worst case a video arrives late, never lost
anything else breaksan external watchdog probes the live system every 30 minutes and runs a full subscribe→handshake drill daily

Live, unfaked numbers: /metrics.

Endpoints

POST /accounts{"email"} → account + one-time API key. Free: 1 feed.
GET /accountplan, feed limit, feeds in use
POST /account/rotate-keynew API key (shown once); old key dies immediately
POST /subscriptions{"channel","callbackUrl"} → subscription + one-time signing secret
GET /subscriptionsyour subscriptions
GET /subscriptions/:idone subscription + recent delivery log (per-attempt HTTP results)
POST /subscriptions/:id/testsigned test.ping through the real pipeline
POST /subscriptions/:id/deliveries/:did/redeliverre-send a failed delivery — payloads are kept
DELETE /subscriptions/:idunsubscribe, free the slot
POST /billing/checkoutStripe Checkout for Pro — $9/mo, 10 feeds
POST /billing/portalcustomer portal — invoices, payment method, cancel anytime

All authenticated calls: Authorization: Bearer fh_…. Machine-readable: openapi.json · llms.txt.

Agents (MCP)

claude mcp add feedhook -e FEEDHOOK_API_KEY=fh_your_key -- npx -y feedhook-mcp

8 tools — an agent can sign up, subscribe a channel, test the receiver, read delivery logs, and upgrade the plan without a human. Listed on the MCP registry.

← a wall on walls.sh