snapcap

@snapcap/native

Unofficial TypeScript SDK for Snapchat Web. Runs Snap's own bundle and WASM modules inside an isolated Node vm.Context — no Playwright, no Frida, no rooted phone.

@snapcap/native is an unofficial TypeScript SDK for Snapchat Web that runs Snap's own JavaScript bundle and WASM modules inside an isolated Node vm.Context. It exposes a browser-free, typed client for authentication, friends, messaging, persistence, throttling, network observability, and multi-account automation — without Playwright, Puppeteer, Selenium, Frida, emulators, or rooted phones.

import { SnapcapClient, FileDataStore } from "@snapcap/native";

const client = new SnapcapClient({
  dataStore: new FileDataStore("./auth.json"),
  credentials: {
    username: process.env.SNAP_USER!,
    password: process.env.SNAP_PASS!,
  },
  browser: {
    userAgent:
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
  },
});

await client.authenticate();
const friends = await client.friends.list();
console.log(friends.map((f) => f.username));

This is research and developer tooling, not affiliated with Snap Inc. Read Safety & risk before pointing this at production accounts.

Where to go next

Why this is different

Most Snapchat-automation projects fall into one of these buckets. @snapcap/native deliberately doesn't.

ApproachShapeWhy this is different
Playwright / Puppeteer / SeleniumFull Chromium per accountWe don't drive a browser at all. Per-account cost is a vm.Context instead of a Chromium process.
Frida / rooted Android / iOSNative Snap binary + instrumentationSnap's mobile risk engine is aggressive. The web target is durable; the mobile target wasn't.
Old "private API" wrappersHand-rolled protobuf callsWe run Snap's own bundle and WASM, not a reimplementation. When Snap rebuilds, the shapes shift but behaviour is the same.
Snap Kit / Login KitOfficial, sanctionedToo narrow — no friend graph, no message send, no story posting.
Userscripts / browser extensionsOne Chrome tabSingle-account, single-tab, no headless, no multi-tenant.

Concretely, @snapcap/native:

  • Runs in native Node (≥ 22 or Bun ≥ 1.3) — no headless browser at runtime.
  • Loads Snap's web JS + WASM into a per-instance vm.Context with a happy-dom Window projected on top.
  • Calls Snap's own WASM for kameleon attestation, the chat session, and Fidelius E2E. We drive the primitives, not reimplement them.
  • Exposes an idiomatic TypeScript surface with per-domain managers (client.friends, client.messaging, …).
  • Multi-tenant in-process. Every SnapcapClient owns its own sandbox, DataStore, and bundle bring-up caches.
  • Pluggable persistence. localStorage, sessionStorage, IndexedDB, and the cookie jar all route through a small DataStore interface — file, memory, or your own backend.

What this is not

  • Not a wrapper around an emulator, Frida, or a rooted phone — those approaches were tried and don't survive Snap's mobile risk engine.
  • Not Snap's official Login Kit or Snap Kit — those don't expose friend management or story posting.
  • Not Playwright-driven. There is zero headless browser at runtime.
  • Not affiliated with Snap Inc. Unofficial, MIT-licensed, research tooling.

Status

The source of truth for what's wired today. See the capability matrix in the README for the complete table; the headline groups:

Working

  • SnapcapClient construction, authenticate(), logout(), refreshAuthToken(), isAuthenticated().
  • client.setStatus(...) / client.getStatus() — global Present / Away / AwaitingReactivate slot.
  • client.friendslist / search / getUsers / snapshot / refresh / sendRequest / acceptRequest / rejectRequest / block / unblock.
  • client.friends.on(...) — typed graph events with offline replay.
  • client.messaging.listConversations() / fetchEncryptedMessages() — direct gRPC-Web, no decrypt cost.
  • client.messaging.on("message", cb) — live decrypted inbound (cached history pump on first subscribe).
  • client.messaging.sendText(convId, text) — outbound text DM via Snap's own bundle send path.
  • client.messaging.setTyping(...) / setViewing(...) / setRead(...) — outbound presence via the bundle's convMgr + presence-slice dual path.
  • Fidelius identity mint + register, per-instance and isolated.
  • Per-instance Sandbox (multi-tenant in one process) — verified by scripts/test-isolation.ts.
  • DataStore-backed sandbox persistence (cookies, local_*, session_*, indexdb_*).
  • Opt-in HTTP throttling (per-instance + shared gates).
  • Structured network observability (setLogger, SNAP_NETLOG=1).

Experimental

  • Live-message push — the persistent duplex WS receives frames, but the bundle's inbound delegate hook is inconsistent across runs. Tracked by tests/api/messaging-multi-account.test.ts.
  • client.messaging.sendImage(...) / sendSnap(...) — scaffolded through the bundle session; compile and bring up cleanly, not wire-tested.
  • client.stories.post(media) — same shape as sendImage; not wire-tested.

Planned

  • client.messaging.on("typing" | "viewing" | "read", cb) — declared on the bus, the bundle's delegate slots are not yet mapped.
  • Per-instance proxy / outbound IP rotation (TODO — BrowserContext.httpAgent reserved for an undici Dispatcher).

Architecture, in one diagram

SnapcapClient (public TypeScript SDK)
  └─ Sandbox (per-instance vm.Context)
      ├─ happy-dom Window projection
      ├─ Patched webpack runtime + chat bundle JS
      ├─ Snap's WASM modules
      │    · kameleon attestation
      │    · chat session worker
      │    · Fidelius E2E (~12 MB + 814 KB)
      ├─ Browser API shims
      │    fetch / XHR / WebSocket / Storage / IndexedDB / cookies
      └─ DataStore-backed persistence
  └─ Native transport (Node fetch + tough-cookie jar — bypasses sandbox)
      └─ gRPC-Web → web.snapchat.com / accounts.snapchat.com / aws.duplex.snapchat.com

See Architecture overview for a paragraph on each layer, or jump straight into the internals chapters for the long-form story.

On this page