Data security

Last updated: 26 September 2026

What we hold, and what we never hold

FeedRobin holds product catalogue data — titles, prices, images, stock, identifiers — and the account details needed to sign in and be billed. That is the whole list.

We never hold your customers' data. No orders, no names, no addresses, no payment details. The Shopify permissions we ask for are read-only and cover products, inventory, markets and translations; nothing in the app reads a customer record, and nothing could, because the access token was never granted that scope.

Where it lives

Application, database and job queue run on Railway, in their EU West region in Amsterdam — your catalogue does not leave the European Economic Area. Everything is reached over HTTPS, and the database is not exposed to the internet at all: it is reachable on Railway's private network by our own service, and by nothing else.

The database has point-in-time recovery, so a restore can be taken to a moment rather than to last night's snapshot.

Encryption

In transit, everything is TLS. At rest, the two secrets that would matter most if a copy of the database ever escaped are encrypted by us before they are written, with AES-256-GCM and a key that lives outside the database: your platform access tokens, and the credentials for any SFTP target you ask us to upload a feed to. Without that key, those columns are noise.

Passwords are not stored, encrypted or otherwise. They are hashed with bcrypt, which only runs one way — we cannot read your password, recover it, or tell you what it is.

Getting in

Sessions travel in a cookie that is httpOnly (JavaScript on the page cannot read it), secure (it never travels over plain HTTP) and sameSite=lax. Changing your password invalidates every session that was open at the time, on every device.

Feed URLs carry an unguessable token rather than sitting behind a login, because a channel fetching a feed cannot sign in. A token can be rotated from the feed's own page, and the previous URL stops working immediately.

The free check, and what it is allowed to reach

The free feed check fetches a URL a stranger typed, which is the most dangerous thing this product does. It resolves the name, refuses every private and link-local address range, and then connects to exactly the address that passed the check rather than resolving the name a second time — so a host whose DNS answers differently a millisecond later cannot use us to reach anything inside our infrastructure. Every redirect hop is validated again, and reads are capped.

A check is anonymous: we keep the feed's host name, whether it succeeded, the score and the product count. Not the URL, not the products, not who ran it. The one exception is a weekly watch you ask for by email, which needs the address and the URL to exist at all, and says so where you ask for it.

Who else touches it

Four companies process data on our behalf, each for one job: Railway (hosting, database, queue), Resend (sending email), Stripe (payments — card details go to Stripe directly and never reach our servers), and Google Analytics plus OpenAI for measurement, both of which stay switched off until you allow them in the cookie banner. Where a conversion carries an email address, the address is SHA-256 hashed before it leaves us.

Our DNS is with Cloudflare. It answers where the site is and nothing else — traffic does not pass through it, so it sees none of your data.

The full list, with what each one is for, is in the data processing agreement.

Leaving

Deleting your account deletes your stores and every product, feed and log attached to them, in the same request. Nothing is kept for a grace period and nothing is archived — so take the export from your account page first, because we cannot bring it back for you afterwards.

Telling us about a problem

Email contact@feedrobin.com. What is in scope, what counts as a finding and what happens next are on the security page, and the machine-readable version is at /.well-known/security.txt.

Live status for syncs, feed pushes and the free check is on the status page, generated from the same data the app runs on rather than updated by hand.