Free plan includes 2 webhook endpoints and unlimited usage.

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

Think of it as a padlock, not a photocopy

A helpful way to picture the transfer:

  1. 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.
  2. 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.
  3. 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:

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:

  1. 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.
  2. 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).
  3. 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.
  4. 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.