Published 2026-08-04 · videoArchiver architecture

Zero-Retention Video: How VideoArchiver Streams Ring Clips to Your Cloud Without Ever Storing Them

Ring and Ring Protect are trademarks of Amazon.com, Inc. VideoArchiver is an independent integration built on Ring's official app-developer APIs.

The problem

Ring cameras are great, but the video they capture has a shelf life. On the default plan, clips expire after 60 days. Ring Protect keeps more of them, but it costs per device, every month, and you still don't own the files in a form you can archive elsewhere. Manually downloading the clips you actually care about is tedious — and by definition you discover which ones matter after the moment passes.

We wanted a set-and-forget answer: every motion event and doorbell press automatically lands in your own cloud storage, permanently, with no monthly storage fee and no manual steps.

That meant building a pipeline that does something most "cloud video" products deliberately avoid: it never stores your video at all.

The core design decision: zero retention

The straightforward way to build a "Ring clip backup" service is to ingest clips into your own object store, then give users an export. That approach:

  • Costs real money per byte stored (your bill scales with your users' video)
  • Makes you a custodian of sensitive camera footage — a giant security and liability surface
  • Puts latency and jurisdiction between the user and their own data

We rejected it. VideoArchiver is built on a zero-retention architecture: clip bytes are fetched from Ring and streamed directly into the user's chosen cloud provider (Google Drive today; Dropbox and S3-compatible storage in the pipeline), passing through our infrastructure in memory only.

What we do persist is metadata — device state, event logs, and encrypted OAuth tokens — so you can see what happened and where it went. We never hold the footage.

+-----------+    webhook    +----------------+   enqueue   +-------------------+
| Ring      | ------------->| webhook-worker |------------>|  Cloudflare Queue |
| device    |   HMAC-SHA256 +----------------+             |  clip-processing   |
+-----------+                                            +---------+---------+
                                                                  |
                                                                  v
                                            +-----------------------------+
                                            |     processor-worker         |
                                            |  token refresh -> fetch clip |
                                            |  fan-out stream              |
                                            +--+----------+----------+-----+
                                               |          |          |
                                               v          v          v
                                          Google     Dropbox     S3-compatible
                                          Drive      (in pipe)   (in pipe)

The stack

ConcernChoiceWhy
ComputeCloudflare Workers (3) + Nuxt 4 SPA on PagesGlobal edge, pay-per-request, no servers to patch
QueueCloudflare Queues (clip-processing + DLQ)At-least-once delivery, retries, dead-lettering, zero ops
DatabaseSupabase Postgres via Cloudflare HyperdriveManaged Postgres + Auth + Row-Level Security; pooled edge connectivity
AuthSupabase Auth (magic link)No password hashing to own; JWT verified at the edge
EmailResendTransactional: welcome, account linked, destination connected, subscription
ObservabilitySentry (Cloudflare + Vue)Errors + traces with user context
Video storageNoneClips stream to the user's cloud, never ours

Every worker talks to Postgres through Hyperdrive — Cloudflare's pooled, edge-local database connection layer — with search_path=videoarchiver. No connection storms, no cold Postgres sessions.

The pipeline, step by step

1. Webhook ingestion (webhook-worker)

When a Ring device fires motion_detected or button_press, Ring POSTs a signed webhook to POST /ring/webhook.

  • Enforces POST + Content-Length, rejects bodies over 100 KB
  • Verifies the X-Signature header as an HMAC-SHA256 over the raw body, compared in constant time (crypto.subtle, no early-exit hex compare) — forged events are rejected before any work happens
  • Normalizes Ring's v1 and v1.1 payload shapes into one internal event type
  • Resolves the user_id from the account link table so every downstream log is tenant-scoped

2. Routing and idempotency

Events split into two buckets:

  • Clip events (motion_detected, button_press) → enqueue a job
  • State events (device_online/offline, device_added/removed, subscription_activated/deactivated) → update device rows and fire subscription emails

Ring's webhooks are at-least-once — the same event can arrive multiple times. We defend with three layers of idempotency:

  1. Queue-level — every job is sent with idempotencyKey = request_id on the Cloudflare Queue
  2. Table-level — a processed_events table short-circuits webhooks we've already seen
  3. Log-levelclip_event_logs has a UNIQUE(request_id, destination_provider) constraint; result writes are idempotent upserts

This is the unglamorous work that makes a distributed pipeline safe: no matter how many times a message is delivered, each clip is archived exactly once per destination.

3. Processing (processor-worker)

The consumer pulls batches (max_batch_size = 5) off clip-processing and, per job:

  1. Free-tier gate — checks the monthly event cap (50 events/month on Free; Pro per-device bypass) before spending a Ring download
  2. Token refresh — decrypts the stored Ring OAuth token; if it's within 5 minutes of expiry, refreshes it via Ring's OAuth endpoint and re-encrypts + persists it
  3. Clip fetch — POSTs {timestamp, duration} to Ring's media download API (api.amazonvision.com/v1/devices/{id}/media/video/download) and streams the response body
  4. Fan-out — one fetch, N destinations: the stream is split through backpressure-aware TransformStream branches, one per configured provider
  5. Upload — each branch streams into its destination (Google Drive resumable upload; Dropbox /2/files/upload; S3-compatible PUT with optional SigV4 signing)
  6. Log — upserts the per-destination result (completed / error / skipped) with storage path, retry count, and timing

Storage filenames are deterministic: <device_name>/<yyyy>/<mm>/<dd>/<epoch>_<event_type>.mp4 — a chronological, browsable archive in your own Drive.

4. Failure handling: retries, DLQs, and replay

Queues give us at-least-once with max_retries = 5 and backoff. But "retry forever" is worse than "give up honestly," so we classify failures:

  • Transient (e.g., a destination API hiccup) → retry(delaySeconds: 60–120)
  • Permanent (no account link, unknown provider, clip already gone 403/404/416) → written straight to a dlq_messages table, job acked
  • Exhausted (5 attempts exhausted) → lands on clip-processing-dlq, persisted as exhausted

The DLQ isn't a black hole — it's a first-class dashboard surface. Users (and we) can inspect dead-lettered jobs, see the reason, and hit replay to re-enqueue them after fixing the underlying problem (an ownership-checked, replayed_at-guarded operation).

This is the reliability story in one sentence: we assume every integration will fail at 3 AM, and we build the recovery path before we need it.

Security model

  • OAuth, not passwords — Ring account linking uses Ring's official OAuth flow with a three-state lifecycle (unclaimed → awaiting → completed) plus a cryptographically-signed nonce (HMAC over timestamp:accountId, 10-minute validity) so the person finishing the claim is the person who started it
  • AES-256-GCM token encryption at rest — every Ring and destination token is encrypted with a random 12-byte IV before it touches Postgres; the key never leaves the edge
  • TLS everywhere in transit — clip bytes move Ring → Worker → user's cloud over HTTPS only
  • Row-Level Security — all six tables enable RLS; authenticated users can only SELECT their own user_id rows, and workers use service-role credentials server-side
  • Transparency as a feature — the user consents to the exact Ring data we read, and the architecture page documents the full flow

Tokens are scoped per account, revoked on deauthorization, and never logged in cleartext.

What this costs (the honest answer)

Because we never touch the video, our marginal cost per clip is infinitesimal — a few KB of metadata and a few queue + worker invocations. That's what makes a free tier with no credit card viable: our costs don't scale with user storage.

This is a genuinely different unit economics shape from a hosted-video product, and it's the whole reason the product can exist as a micro-SaaS at all. If we had to pay for every user's footage, the free tier would be an act of charity rather than a pricing strategy.

Tradeoffs and what we'd do differently next time

Being honest about the sharp edges is part of building credibility with engineers:

  • Streaming fan-out amplifies the slowest destination. If Drive is down and Dropbox is fine, the whole job retries. Per-destination independent delivery (separate jobs) would isolate failures — at the cost of double-fetching the clip from Ring. The current tradeoff favors minimizing Ring API load.
  • A single data-encryption key encrypts all tokens today. It's operationally simple and the key is environment-injected, never in code, but the blast radius of a key compromise would be reduced by a key-encryption-key (KEK) hierarchy and rotation support. That's on the roadmap.
  • Free-tier limits are enforced per device — and we kept them honest. The limiter counts events per ring_device_id, matching the "50 events/device/month" copy. When the audit found the code counting per account instead, we fixed the limiter to match the claim rather than watering the claim down. A Pro device bypasses the cap entirely; free devices on the same account don't share a pool.
  • No in-transit transforms. We archive the clip exactly as Ring delivers it. Transcoding, thumbnailing, or AI tagging would require us to hold the bytes (or send them to a third party), which breaks the zero-retention invariant. We'd only do that opt-in, with user consent.

The boring stuff that actually matters

Three things that don't make the HN title but make the product real:

  1. Idempotency everywhere — at-least-once delivery is only safe if every consumer is idempotent. We treat that as a hard rule, not a nice-to-have.
  2. The DLQ has a UI. Dead letters that nobody can look at are just a quieter outage. Ours shows the payload reason and replays with one click.
  3. Observability before scale — Sentry traces through every fetch, refresh, fan-out, and upload step, with setUser so a single failing account is findable. If you can't measure a pipeline, you can't debug it at 3 AM.

Try it

Free tier: 50 events/month per device, no credit card. Link your Ring account, connect Google Drive, done — every clip archived forever, in your own cloud, on your own infrastructure.

Get Started Free

Not affiliated with Ring or Amazon. Ring is a trademark of Amazon.com, Inc. Your cloud storage costs are determined by your provider (Google Drive's free 15 GB, for example) — not by us.