@cookiemunch/sdk
A small, typed REST client for the Cookie Munch Developer API (the /v1 surface).
Authenticate with an API key — the organization is derived server-side from the key,
so you never pass an orgId.
import { createCookieMunch, CookieMunchApiError } from '@cookiemunch/sdk';
const fc = createCookieMunch({
apiKey: process.env.COOKIEMUNCH_API_KEY!, // "fck_…"
baseUrl: 'https://api.cookiemunch.net', // "/v1" is appended automatically
});
const sites = await fc.sites.list();
const site = await fc.sites.create({ domain: 'example.com' }); // cbid auto-generated
const stats = await fc.consent.stats(site.cbid, { from: 0, to: Date.now() });
try {
await fc.sites.get('does-not-exist');
} catch (e) {
if (e instanceof CookieMunchApiError) console.error(e.status, e.message);
}
fetch is injectable via createCookieMunch({ ..., fetch }) for tests or custom
runtimes; it defaults to the global fetch.
Methods
me()sites.list() / create(input) / get(cbid) / delete(cbid) / getConfig(cbid) / putConfig(cbid, config)sites.lintConfig(cbid, config, { replace? })— consent-button parity: is Accept all pushed harder than Reject all? ReturnsdesignWarnings,score,suggestedReject; nothing is saved. Every config read/write also carriesdesignWarnings.consent.stats(cbid, {from?,to?}) / log(cbid, {from?,to?,limit?}) / export(cbid, {from?,to?}) / receipt(cbid, stamp)dsar.list() / create(input) / advance(id, toStatus)vendors.list() / create(input)ropa.list() / create(input)webhooks.list() / create(input)rightsPage.get(cbid) / put(cbid, page, {baseUpdatedAt?}) / patch(cbid, patch, {baseUpdatedAt?}) / dryRun(cbid, page, {mode?}) / getPublished(cbid) / publish(cbid, {baseUpdatedAt?}) / versions(cbid) / rollback(cbid, version, {baseUpdatedAt?})— pass the draft'supdatedAtasbaseUpdatedAtso a newer save is a 409draft_conflict, never overwritten or published unseen; a rollback replaces the draftrightsPage.listDomains(cbid) / addDomain(cbid, hostname) / verifyDomain(cbid, hostname) / removeDomain(cbid, hostname)— the rights page on your own hostnamerightsLinks.create(cbid, { subject, rights?, ttlSeconds? })— a signed, single-use rights-page link for a signed-in personorg.mailProvider.get() / set(input) / test(to?) / remove()— your own provider for rights-request mail
These are the most used; the client covers every /v1 operation (see the typed CookieMunchClient).
export() returns the raw CSV string; all other methods return parsed JSON. Any
non-2xx response throws a CookieMunchApiError { status, code?, message }. Error code
values are catalogued in docs/api/error-codes.md.
Verifying webhooks
verifyWebhookSignature(...) validates an inbound webhook's X-CookieMunch-Signature
(HMAC-SHA256 over `${timestamp}.${rawBody}`) and its freshness. It's the authoritative
verifier — always matches the server. Fail-closed; returns a boolean.
import { verifyWebhookSignature } from '@cookiemunch/sdk';
const ok = verifyWebhookSignature({
payload: rawBody, // exact request body bytes (a string)
signature: headers['x-cookiemunch-signature'],
timestamp: headers['x-cookiemunch-timestamp'],
secret: process.env.CM_WEBHOOK_SECRET!, // the whsec_… shown once on create
});
Full recipe (Express, edge/WebCrypto, replay protection): docs/api/webhook-signing.md.
Answering rights requests
createRightsHandlers(...) from @cookiemunch/sdk/rights serves the
cm-standard-v2 executor contract as Fetch API
handlers built from your callbacks. Cookie Munch then finds, exports and erases people in your product.
import { createRightsHandlers } from '@cookiemunch/sdk/rights';
export const { GET, POST } = createRightsHandlers({
key: process.env.CM_EXECUTOR_KEY!, // the secretKey you registered
idSecret: process.env.CM_REQUEST_ID_SECRET!, // signs request ids; 32+ characters
lookup: ({ emails, identity }) => findUserId(emails, identity),
access: async (r) => ({ status: 'completed', result: await exportUser(r.subjectId) }),
erase: async (r) => { await deleteUser(r.subjectId); },
store: fileStore('/var/lib/myapp/rights'), // import it too; or sqlStore({ query }) / redisStore(client)
});
The default store is in memory. A scheduled erasure has no timer in your process: erase runs on
Cookie Munch's next poll after executeAfter (every 10 minutes), so production needs a durable
store: fileStore(dir), sqlStore({ query }) (table: RIGHTS_STORE_TABLE_SQL) or
redisStore(client).
Add completion: { url, secret } (the connection's completionsUrl and completionSecret) and each
request that reaches a final status is pushed to Cookie Munch as a signed completion, so it closes in
seconds. Close a pending or processing request later with await handlers.complete(requestId, outcome).