OAuth callbacks. Sealed webhooks.
cc.me redirects OAuth callbacks to local targets and keeps webhook deliveries encrypted until you fetch them.
Use cases
-
Local OAuth development.
Register one HTTPS redirect URI, then trampoline callbacks to a local app like
http://example.local/auth/callback. -
Strict provider rules.
Keep working with providers that require HTTPS callbacks or reject private names such as
.local. -
Webhook inboxes.
Point a provider at
/under/<public-key>, then pull encrypted deliveries when your app is ready. - Debugging retries. Catch small payloads while a service is offline or changing, with recent drop counts when retention trims old deliveries.
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.
POSTcaptures a request and stores it encrypted for that public key.- Retention is best-effort and service-owned; the starting defaults keep up to 100 hooks for up to 24 hours.
- Requests are stored only when the captured method, path, query, headers, and body fit within 64 KiB; larger requests are rejected.
GETdequeues oldest-first.-
Add optional
?l=10to set the maximum batch size and/or?pto wait when nothing is ready.
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());
}