{"slug": "what-i-learned-building-an-ai-background-remover-on-cloudflare", "title": "What I Learned Building an AI Background Remover on Cloudflare", "summary": "A developer built CutoutKit, a web app on Cloudflare that removes image backgrounds and returns transparent PNGs. The project required authentication, usage limits, payments, and privacy boundaries, with a focus on atomic credit reservation to prevent race conditions and a proxy architecture to keep API keys secure.", "body_md": "I recently shipped [CutoutKit](https://imagebackground.site/), a small web app that removes image backgrounds and returns a transparent PNG.\n\nThe interface is intentionally simple: upload an image, wait a few seconds, and download the result. The implementation behind that flow was less simple. Once I moved past the prototype, I needed authentication, usage limits, payments, webhook reliability, privacy boundaries, and an architecture that could run comfortably on Cloudflare.\n\nThis post covers the decisions and mistakes that were most useful to me. It is not a tutorial for a specific starter repository; it is a set of patterns you can reuse in other API-backed SaaS products.\n\nI started with a few deliberate constraints:\n\nThe resulting request flow looks like this:\n\n``` php\nBrowser\n  |\n  +--> Next.js UI on Cloudflare\n  |\n  +--> Cloudflare server endpoint\n          |-- verify session and request\n          |-- reserve one usage credit in D1\n          |-- send the image to remove.bg\n          |-- stream the processed PNG back\n          `-- release the reservation if processing fails\n\nPayment provider webhook\n  `--> verify signature --> record event --> grant credits in D1\n```\n\nOne important privacy detail: “not stored” does not mean “never leaves the browser.” The uploaded file is sent through my server endpoint to the image-processing provider. My application does not write the original or processed image to a database, object store, or disk, but the processor still receives it to perform the requested operation. That distinction belongs in both the product copy and the privacy policy.\n\nThe first production rule is obvious but easy to violate during a quick prototype: never call a paid processing API directly from browser code if it requires a secret key.\n\nThe browser uploads a `multipart/form-data`\n\nrequest to a same-origin server endpoint. That endpoint reads the remove.bg key from a Cloudflare secret, adds it to the upstream request, and returns the image response. The key is never included in the JavaScript bundle or exposed through a public configuration endpoint.\n\nThe proxy also became the right place to enforce the application’s rules:\n\n`multipart/form-data`\n\n.Client-side validation still improves the experience, but it is not a security boundary. Every meaningful rule is repeated on the server.\n\nUsage accounting looks trivial until two requests arrive at nearly the same time.\n\nA fragile implementation follows this sequence:\n\n```\nread remaining credits\ncall expensive API\nincrement usage\n```\n\nTwo concurrent requests can both see the same remaining credit and both proceed. The better pattern is to reserve usage before calling the paid API:\n\n```\nreserve credit atomically\ncall upstream processor\nconfirm reservation on success\nrelease reservation on failure\n```\n\nThis gives the request a small transaction-like lifecycle even though the database operation and external API call cannot share a real database transaction.\n\nThe failure path matters as much as the success path. A timeout, rejected file, upstream outage, or exhausted provider balance should not charge the user. In my handler, upstream failures trigger a best-effort release of the reservation before returning a normalized error to the client.\n\nThis pattern is useful for any metered API product: image generation, transcription, document conversion, email verification, or LLM calls.\n\nGoogle OAuth is conventional, but custom domains and preview domains create a sharp edge: the redirect URI must match exactly.\n\nMy application builds the callback from a single canonical application origin:\n\n```\nhttps://imagebackground.site/api/auth/google/callback\n```\n\nThat exact URI must appear in the Google OAuth client configuration. Differences in protocol, hostname, port, path, or trailing characters cause `redirect_uri_mismatch`\n\n.\n\nThe login flow uses an OAuth state value stored in a short-lived, HTTP-only cookie. The callback compares that cookie with the returned state before exchanging the authorization code. After retrieving the Google profile, the app upserts the user in D1 and creates a server-side session.\n\nThe browser receives a signed session identifier in a secure, HTTP-only cookie. It does not receive the Google access token, and the application does not need to keep that token after fetching the profile.\n\nD1 stores only the durable product data the app needs: the Google subject identifier, email, display information, sessions, usage events, and purchases.\n\nI chose 30-day credit packs rather than automatic subscriptions. The current model has a small free allowance and two paid packs. A purchase adds a fixed number of credits with an expiry date; it does not create recurring billing.\n\nSupporting both PayPal and Creem helped me separate the payment provider from the entitlement model. The provider collects money, but my own database decides whether a user may process an image.\n\nThe common flow is:\n\n`pending`\n\n.The browser never submits an authoritative amount or number of credits. It submits only a plan identifier, and the server looks up the plan details.\n\nA payment success page is useful for the user experience, but it is not a reliable source of truth. The customer can close the tab, lose connectivity, or manipulate a browser request. Webhooks are the durable confirmation channel.\n\nMy webhook handlers follow four rules:\n\nThis is idempotency in practical form. Payment providers retry webhooks, and your endpoint must assume the same event can arrive more than once.\n\nI also store processing status for each webhook event. If an event fails halfway through, it is visible and can be retried without pretending it never arrived.\n\nThe public pages are statically rendered, while the authenticated and image-processing routes run at the edge. Cloudflare D1 provides the relational state, and environment secrets keep provider credentials out of source control.\n\nThe deployment also has one canonical host. Requests for the `www`\n\nhostname and the Pages preview hostname redirect to the apex domain. This fixed several subtle issues at once:\n\nI added a generated sitemap, `robots.txt`\n\n, canonical metadata, structured data, a favicon, and focused landing pages for real use cases such as product photos and transparent PNG creation. SEO did not change the application architecture, but the canonical-host decision definitely did.\n\nIf I built another API-backed SaaS, I would make these decisions before polishing the interface:\n\nThe background-removal model was not the hardest part of this product because I deliberately used a specialized API. The interesting engineering work was everything around it: protecting the API boundary, accounting for usage correctly, handling identity at the edge, and turning payment notifications into reliable entitlements.\n\nThat is also encouraging. A small product does not need a huge infrastructure stack, but it does need clear trust boundaries and careful state transitions.\n\nYou can try the finished implementation at [CutoutKit](https://imagebackground.site/). If you are building a similar API-backed tool, I would be interested to hear which part caused the most trouble for you.", "url": "https://wpnews.pro/news/what-i-learned-building-an-ai-background-remover-on-cloudflare", "canonical_source": "https://dev.to/fu_bront_a2bc935343b8b06e/what-i-learned-building-an-ai-background-remover-on-cloudflare-2l74", "published_at": "2026-07-30 16:15:01+00:00", "updated_at": "2026-07-30 16:35:40.533320+00:00", "lang": "en", "topics": ["developer-tools", "ai-products"], "entities": ["Cloudflare", "CutoutKit", "remove.bg", "Google"], "alternates": {"html": "https://wpnews.pro/news/what-i-learned-building-an-ai-background-remover-on-cloudflare", "markdown": "https://wpnews.pro/news/what-i-learned-building-an-ai-background-remover-on-cloudflare.md", "text": "https://wpnews.pro/news/what-i-learned-building-an-ai-background-remover-on-cloudflare.txt", "jsonld": "https://wpnews.pro/news/what-i-learned-building-an-ai-background-remover-on-cloudflare.jsonld"}}