Security

Quiet outbound, kept private.

We are a small team building software that touches your LinkedIn account and the people you sell to. The specifics below are the actual rules in our code today, not aspirations. If you find a hole in any of them, we want to hear about it.

Authentication

Passwords are hashed with bcrypt at cost factor 10. We never log, store, or transmit passwords in plain text. Sign-in produces a short-lived access token (15 minutes, HS256-signed JWT) and a longer refresh token (7 days). Tokens are issued and validated on the Go backend; the Next.js layer never holds them in memory longer than a request.

Single sign-on with Google and LinkedIn is supported and uses the standard OAuth 2.0 flow. We store only the tokens we need to identify you on return visits.

How your LinkedIn tokens are stored

When you connect a LinkedIn sender, the authorization tokens are encrypted at rest with AES-256-GCM using a fresh nonce per write. The encryption key is held in a server-only environment variable (32 bytes, hex-encoded), never checked into the codebase, never transmitted to the frontend.

Decryption happens only inside the Go backend at the moment a send is queued. The Next.js layer never sees an unencrypted LinkedIn token. If our database is ever stolen without the encryption key, the attacker has ciphertext and nothing else.

Webhooks

Outbound webhooks (the events we send from Funkel to your endpoints) require HTTPS at subscription time. HTTP URLs are rejected at create and at update. Every payload is signed with HMAC-SHA256 against a per-subscription secret; the signature lands in the X-LeadPilot-Signature header so your handler can verify the request before acting on it. See Verifying webhook signatures for the implementation.

Inbound webhooks (the events we receive from LinkedIn integrations) are verified the same way before we accept them. A request without a valid signature is dropped and never reaches the database.

Data isolation

Every query that returns user-owned data is scoped to the authenticated user’s ID at the SQL level. The scoping is generated as part of our query layer, not applied loosely in handlers, so a missing filter is a compile-time problem, not a runtime one. Cross-user access is not a feature you can opt into; it does not exist in the surface.

Internal jobs and engine workers that operate on data on your behalf use a separate set of unscoped queries that read the user ID from the job’s arguments, not from a request session. The split keeps user-facing handlers strict while still letting background work proceed.

Account safety as a security feature

A flagged LinkedIn sender is a security incident from your perspective: you lose your professional network until the account is restored. We treat pacing rules as part of the security stack, not a marketing point.

Each sender has weekly caps under LinkedIn’s soft limits (80 invites and 80 messages by default, below LinkedIn’s 100/150 cap). New senders ramp up over fourteen days from 20% to 100% of the daily budget. Sends are jittered between 30 and 180 seconds apart. Withdrawn invites trigger a 21-day cooloff before the same lead can be contacted again. The full rules are documented in Daily limits and account safety.

Payment data

Card numbers and bank details never touch our servers. Billing is handled by Stripe; we store only the references they give us (customer ID, subscription ID, payment method ID). Cancellation, updates, and disputes happen through the Stripe-hosted portal, not through code we wrote.

What we do not yet have

We are honest about the parts of a security program that are still in front of us. As of today, Funkel does not have a SOC 2 Type II report, an ISO 27001 certification, or a published Data Processing Addendum. We are early. If you need any of the above to evaluate Funkel, write to us at hello@funkel.ai and we can talk about timelines, what we have in place today, and what compensating controls work for your review process.

If you find a vulnerability

Email security@funkel.ai with details. We respond to every report. We will credit you once the issue is fixed, with your permission, and we will not pursue legal action against good-faith research.