How Device Transfer Protects Your Webhook Secrets
ShareToWebhook lets you move your saved webhook endpoints between the browser extension and the mobile app with a six-character code or a quick QR scan. No re-typing URLs, no emailing yourself a file.
If you use custom headers, your configuration can include sensitive things - API keys, bearer tokens, signing secrets. So it's completely reasonable to ask: is my data secure? does my configuration sit on your server, where someone could hack it and get my API tokens?
Short answer: no. Your configuration is encrypted before it leaves your device, and only your other device can unlock it. Our server passes the sealed package along without ever being able to look inside. Here's how that works - in plain language first, with the full technical detail at the end for those who want it.
The short version
- Your config is encrypted before it leaves your device. What travels through our server is scrambled data that is meaningless without the key.
- Only your receiving device holds the key. The key is created fresh for each transfer, never leaves that device, and is destroyed the moment the transfer finishes.
- We couldn't read your configuration if we tried. We never have the key - there is nothing on our server to "hack" that would reveal your secrets.
- Nothing lingers. Transfer codes expire after 10 minutes. The sealed package is deleted as soon as your device collects it, and anything uncollected is automatically wiped shortly after expiry.
- Every code works exactly once. A code can't be reused, guessed within its lifetime in any practical way, or replayed later.
- No account required. The transfer isn't tied to your identity - the server briefly holds an anonymous sealed package, and that's all.
Think of it as a padlock, not a photocopy
A helpful way to picture the transfer:
- The device that's receiving your configuration creates a brand-new padlock and keeps the only key. It sends just the open padlock to our server - a padlock is useless for opening anything.
- The device that's sending your configuration fetches that padlock, puts your webhook settings in a box, and snaps the padlock shut. Once it clicks closed, nothing can open it except the key - which never left your receiving device.
- Our server plays courier. It holds the locked box for a few minutes at most, hands it to your receiving device, and then destroys any record of it.
At no point does the courier have the key. That's the whole design: even a complete copy of our server's database would contain nothing but locked boxes with no keys anywhere in sight.
What our server sees - and what it never sees
It sees: that a transfer happened, when it happened, and a sealed blob of scrambled data (up to 64 KB) that it cannot open.
It never sees: your webhook URLs, your endpoint names, your custom headers, your API keys or tokens - or any way to connect a transfer to who you are.
Why is a server involved at all?
A fair question - if the configuration is only meant for your other device, why doesn't it just travel straight there?
Because of all the issues around networking, firewalls, bluetooth connections, and so on.
A small relay in the middle solves all of that with a single six-character code: it works on any network, doesn't need a camera on a laptop or PC, needs no pairing or setup, and the two devices don't even have to act at precisely the same moment - the sealed package waits a few minutes for your receiving device to collect it.
The important part is that this convenience costs nothing in privacy. The relay was designed on the assumption that it should *never need to be trusted with your data*: it carries a locked box it cannot open, holds it for minutes at most, and then destroys it. The server is involved in the delivery - never in the contents.
Minutes, not months
Every transfer is deliberately short-lived:
- A transfer code is valid for 10 minutes. After that it's dead, whether or not it was used.
- Once your receiving device collects the package, the server deletes it — with only a brief grace period (about a minute) to survive a flaky mobile connection.
- If you cancel a transfer, or a transfer completes, the apps actively tell the server to delete the channel straight away rather than waiting for it to expire.
- A background job sweeps the server every 10 minutes and removes anything left over.
There is no archive, no backup of transfer payloads, no "deleted items" folder. Gone means gone.
An extra safeguard when you scan the QR code
When you scan the extension's QR code with the mobile app instead of typing the code, the QR carries the padlock itself along with the code. The sending app then double-checks that the padlock our server hands it is exactly the one in the QR. That means even a tampered-with server couldn't swap in a padlock of its own — the transfer would simply refuse to start.
The technical details
For readers who want specifics, here is exactly what happens under the hood.
Encryption. Transfers use NaCl sealed boxes (the libsodium crypto_box_seal construction): X25519 key agreement with an ephemeral sender key, XSalsa20 encryption, and Poly1305 authentication. We use audited TweetNaCl implementations in both the extension and the mobile app. Sealed boxes provide anonymous, authenticated encryption to a public key: anyone can seal, only the holder of the matching secret key can open, and any tampering with the ciphertext causes decryption to fail outright.
Key lifecycle. The receiving app generates a fresh X25519 key pair per transfer, in memory only. The secret key is never written to disk, never leaves the device, and is explicitly zeroed when the transfer completes, is cancelled, or expires. Only the 32-byte public key is uploaded.
The exchange, step by step:
- Receiver → server: the receiver posts to our server with the public key. The server issues a channel (random UUID) and a 6-character one-time code, valid for 10 minutes.
- Sender → server: looks up the channel by code and retrieves the public key. It serialises the webhook configuration into a JSON envelope, seals it to that public key, and uploads the ciphertext (capped at 64 KB decoded).
- Receiver: polls the channel every 3 seconds, receives the ciphertext, and decrypts locally. Decrypted data is validated against the same rules as manually entered configuration before anything is saved.
- Cleanup: the channel is deleted by the client on completion or cancellation, and by a scheduled purge shortly after expiry otherwise. Delivered ciphertext remains re-fetchable for a 60-second grace window after first pickup purely for network resilience, then is purged.
Codes. Codes use Crockford base32 (no I, L, O or U, so they're unambiguous when read aloud), giving a space of 32⁶ ≈ 1.07 billion combinations for a code that lives at most 10 minutes and dies on first use. All transfer endpoints are rate-limited per route, which makes online guessing impractical by many orders of magnitude.
Delivery semantics. A channel accepts exactly one payload: the pending → delivered transition is atomic, and a second sender receives a conflict error. Once collected (plus the grace window), the ciphertext is gone from the database entirely - the row is deleted, not flagged.
Key pinning via QR. The extension's QR code encodes {code, public_key}. When the mobile app sends after scanning, it compares the scanned public key against the one returned by the server's lookup and aborts on any mismatch. This removes the server from the trust equation for key exchange in the scanned flow: a malicious or compromised server attempting key substitution is detected before anything is encrypted.
Transport. Everything above rides over HTTPS as well, so the sealed payload is encrypted in transit twice - but the design does not rely on transport security: the end-to-end sealed box is what protects your secrets.
What a breach would expose. If our transfer database were stolen, the attacker would obtain: short-lived rows containing a code, a public key, timestamps, and (for in-flight transfers) an undecryptable ciphertext. No secret keys, no plaintext configuration, no user identity - the transfer endpoints don't require or record an account.
If you have questions about any of this, please get in touch.