cc.me

OAuth callbacks. Sealed webhooks.

cc.me redirects OAuth callbacks to local targets and keeps webhook deliveries encrypted until you fetch them.

Use cases

Trampoline

Use / with an absolute at target. Every callback query parameter except at is appended to the target.

https://cc.me/?at=http%3A%2F%2Fexample.local%2Fauth%2Fcallback

Safe inbox

/under/<base64url-public-key> is a tiny encrypted queue.

POST /under/6JgMhuAy8espdQUujWW93RXDtZZBF07JZ4pTeJ2Sx1Q
GET  /under/6JgMhuAy8espdQUujWW93RXDtZZBF07JZ4pTeJ2Sx1Q

A dequeue response includes only as many items as fit within 1 MiB.

{
  "count": 1,
  "items": [
    "base64url-encrypted-delivery"
  ],
  "dropped_last_day": 0
}

After decrypting an item with your private key, the payload has this shape.

{
  "received_at_unix_ms": 1781337600000,
  "method": "POST",
  "path": "/under/...",
  "query": "source=github&delivery=42",
  "headers": [
    {
      "name": "content-type",
      "value_b64u": "YXBwbGljYXRpb24vanNvbg"
    }
  ],
  "body_b64u": "e30"
}

JS client

Forward a Safe inbox to a local endpoint with the cc-me command:

npx cc-me http://example.local:8080/webhook
pnpx cc-me http://example.local:8080/webhook
bunx cc-me http://example.local:8080/webhook
deno run -A npm:cc-me http://example.local:8080/webhook

The forwarder persists and reuses ~/.cc-me.key by default. Use --key to choose a different key file:

npx cc-me --key ~/hooks.key http://example.local:8080/webhook

The package works as ESM in Node.js, Bun, and Deno. privateKey() creates an in-memory key; privateKey(path) creates a key file private to the user if it does not exist yet, then reuses it on later runs.

import { homedir } from "node:os";
import { CcMeClient, privateKey } from "cc-me";

const key = await privateKey(`${homedir()}/.cc-me.key`);
const cc = new CcMeClient({ privateKey: key });

console.log(await cc.underUrl());

const { requests, dropped_last_day } = await cc.dequeue({
  limit: 10,
  poll: true,
});

for (const request of requests) {
  console.log(request.method, request.path, request.text());
}

Stats

Redirects, 48h

Inboxes, 48h

Inboxed, 48h

Redirects, 30d

Inboxes, 30d

Inboxed, 30d