sahilx.dev

Hi, I am Sahil.

Backend engineer at Pictory, working on billing and payments. Remote from India (IST).

Pictory is an AI video product. I joined as a trainee in 2022 and moved to the backend team in 2023. Most of my work since has been the billing stack: subscriptions and plan changes, checkout, affiliate payouts, and the credits that meter AI usage.

We are a small team, so I usually get to see a system through from design to rollout and the fixes after. The write-ups below say where others did part of the work.

Work

Four systems in production.

01INR billing and RBI e-mandates

led the backend work
Context

The Reserve Bank of India's e-mandate rules require recurring card debits to run against a registered mandate, with cardholder authentication at registration and a notification to the cardholder before each debit. Our subscriptions billed in USD with no mandate, so renewals on Indian cards failed.

What I built

INR as a second billing currency, e-mandate registration for Indian cards through the payment provider, and mandate-based recurring debits for renewals. The billing system had assumed one currency and became multi-currency.

Result

Indian customers subscribe on local cards again; renewals debit a registered mandate.

02Credits and quotas for AI usage

designed and built
Context

Every plan includes an allowance of credits spent on image, video and audio generation. Several generation paths spend from the same balance, so the check and the debit have to be consistent or a user can spend more than they have.

What I built

One credits service that every generation path debits through. The balance check and the debit happen as one operation, so two concurrent jobs cannot both pass the check. The same service handles plan changes: an upgrade or downgrade adjusts the customer's allowance.

Decisions
  • One debit path rather than a quota check per generation path. Having several call sites with slightly different checks is how overspend happens.
Result

Usage is enforced in one place, and upgrades and downgrades go through the same service.

03Affiliate payouts from Stripe and PayPal webhooks

built the backend
Context

Affiliates earn a commission on referred purchases. Purchase events arrive as Stripe and PayPal webhooks, and both providers redeliver on timeout, so a payout path that isn't idempotent will eventually pay someone twice.

What I built

Webhook consumers for both providers, the payout APIs, and abuse detection on referrals. The APIs that create payouts are idempotent: a redelivered webhook or a retried request is a no-op, not a second payout.

Result

Duplicate payouts are prevented by design, and referral abuse is caught before a payout is approved.

04Checkout before signup

React checkout and backend purchase path
Context

Buying a plan took about two minutes and required creating an account before paying.

What I built

A purchase path where payment comes first and the account is attached afterwards, with Redis caching on the purchase path.

Result

Time to complete a purchase went from about two minutes to about ten seconds, measured before and after the change.

Also

  • Editor memory. Virtualised the visuals tab of the video editor so only on-screen assets are rendered. Memory on a project with about 1,000 assets went from roughly 1 GB to 138 MB, measured before and after.
  • Braintree. Integrated it as an additional card processor with one other engineer and took it live for new customers.
  • Cancel flow. A churn-mitigation step in the cancellation path, instrumented in Amplitude. Same area: reactivation and account data deletion.
  • Trainee year. An internal support tool (Lambda, DynamoDB, Flask, React) that automated common ticket resolutions, including a generic DynamoDB update-expression builder so a new form field didn't need a backend deploy. Also wrote and tested the APIs (Jest, Frisby) exposed to an enterprise customer.

On my own time

Python and LLM systems.

Stripe Webhook Triage Agent

Python · FastAPI · Postgres · SQLAlchemy async · Pydantic v2

A webhook reliability layer with an LLM on the failure path only. It verifies the Stripe signature with a timestamp tolerance window, stores each event once under a unique constraint on the event id so redeliveries are no-ops, then processes it. Each failed attempt bumps a counter; at three the event is dead-lettered. Retries are explicit: any event can be replayed from its stored payload, re-running the existing row rather than re-ingesting it.

Dead-lettered events are triaged on demand, one per call or in batch, with a single forced-tool-use LLM call that classifies them as transient, config error, genuine payment failure, or needs a human, and writes a suggested action and a self-reported confidence back to the row. The classifier backend is pluggable: the Anthropic Messages API or any OpenAI-compatible endpoint. A 500-event synthetic corpus with held-out labels, the eval harness, the seeder and 20 tests (ingest, replay, signature, classifier contract) ship with the project.

  • Yapp. A support chatbot you point at your own docs. Ingests URLs, sitemaps, PDFs and YouTube transcripts through a job queue, chunks and embeds into pgvector, answers with retrieval, and ships as an embeddable chat widget. Next.js, TypeScript, Prisma, Supabase.
  • PostureGuard. Runs MediaPipe pose in the browser via WASM, never sends video anywhere, and alerts after one second of confirmed slouching. Vanilla JS, Chrome MV3.
  • Deep Resume. Resume editor with streamed LLM suggestions, autosave and PDF export. Next.js 15, Prisma, Postgres.

Now

What I reach for these days.

At work
Node.js, TypeScript, Postgres, DynamoDB, Redis, AWS (Lambda, Step Functions, CDK), Stripe, PayPal, Braintree, Chargebee
On my own time
Python, FastAPI, Pydantic, pgvector, the Anthropic and OpenAI APIs, evals

Contact

Feel free to connect via the platforms below.

My local time is .