Inspiration

Ring cameras and doorbells are good at noticing movement. The problem is what happens next: a delivery, a neighbour's cat, someone coming home, the wind in the backyard. Every one of them becomes an alert. After a few weeks most people stop reading them, and that is exactly when the one alert that matters gets missed.

We wanted to flip the question. Instead of asking "did something move?", ask "is this different from how this home normally behaves?"

The second thing that pushed us was a quieter case. Think of someone whose mother lives alone. The most important signal there is not a motion alert. It is the absence of one: the front door hasn't opened by noon, on a day it usually opens by 8. A camera can't alert on something that didn't happen. A system that knows the routine can.

What it does

VIGILANT receives Ring events, groups them into short sequences, learns what normal looks like for that specific home, and only surfaces the sequences that break the pattern.

  • Noise funnel: raw events → detected patterns → the few worth your attention. In our demo week: 50 events → 17 patterns → 5 flagged.
  • Plain-language explanations: every flag says why, compared with the learned baseline. For example: "Someone left via the front door and came back through the side door 11 minutes later at 8:13 AM. On a normal day the gap is much longer."
  • Caretaking: late_first_activity flags a home whose first activity of the day is much later than usual.
  • Feedback loop: "This was normal" adds the sequence to the baseline, so similar sequences score lower next time.
  • Daily digest and a 7-day activity vs baseline chart.

How we built it

Ring integration. We implemented the Ring Partner API flow: OAuth token exchange, one-way account linking with nonce verification, app-integration confirmation, the devices API, and an HMAC-verified webhook endpoint. Webhooks are deduplicated by request_id, and Ring tokens are encrypted at rest with Fernet.

Signed simulator. For development and the demo we built a simulator that sends HMAC-signed webhooks in Ring's payload format to our real /ring/webhook endpoint. Simulated events go through exactly the same verification, normalization, dedup and scoring code as live Ring events. Nothing writes straight to the database.

The MOTIF engine. It is inspired by habituation in the fruit fly: stimuli that repeat fade into the background, and new ones get attention. Each 5-event sliding window is scored on four layers:

Layer What it measures
Novelty How different the sequence is from the learned baseline
Adaptation How often this sequence has been seen before
Temporal weight How recent the events are
Competition How busy the attention queue is today

Novelty uses Jaccard similarity, so a near-miss of a known routine scores lower than something completely new:

$$ J(A, B) = \frac{|A \cap B|}{|A \cup B|} $$

The layers combine into one salience score from 0 to 10:

$$ S = w_N N + w_A A + w_T T + w_C C $$

Sequences that score low are learned into the baseline automatically. The ones that score high get flagged and classified (rapid return, unusual entrance, loitering, late first activity, late-night activity and others). A rule-based explainer then turns the score breakdown into a sentence a person can act on.

Stack: FastAPI and SQLAlchemy on Vercel serverless, Neon Postgres, and a React + Vite dashboard with Recharts. The test suite covers HMAC edge cases, webhook normalization, deduplication, the OAuth flow, the MOTIF scoring and pattern types, and the full webhook-to-pattern pipeline.

Challenges we ran into

  • Ring account linking. We got through Ring's authorization screen, but the handoff back to our app never completed, so our backend never received a linked account. The one-way flow has several required steps across Ring and our side (token exchange URL, account link URL with nonce, partner sign-in, then confirming the app integration). We spent a lot of time tracing which one failed. We logged the details in our friction log and built the signed simulator so the rest of the pipeline could be tested honestly.
  • Serverless state. Our first deploy used SQLite in /tmp on Vercel. It reset on every cold start, so the baseline could never actually be learned. We moved to Neon Postgres.
  • Counting patterns honestly. Overlapping sliding windows first produced more "patterns" than raw events, and the same sequence showed up several times. We deduplicate overlapping windows now, so the funnel really is a funnel.
  • Time zones. late_first_activity was originally checked against UTC hours, which is wrong for a home in India. "Today" and "time of day" now use the home's configured time zone.
  • Explanations without an LLM. We started with generated explanations and replaced them with a deterministic explainer built from the score breakdown. It's free and instant, and every sentence can be traced back to a number.

What we learned

  • Deciding what to ignore is as important as detecting events.
  • Explanations people trust come from the actual signal that fired, not from nicer wording.
  • With partner integrations, the edges (signatures, nonces, idempotency, retries) take more work than the happy path. Building a signed simulator early made every later bug easier to find.
  • Don't commit secrets, even to a private repo. We did once, and rotated the keys.

What's next

  • Complete live Ring account linking and test on a real doorbell.
  • Per-device sensitivity and quiet hours.
  • Caretaking mode with a trusted contact who gets the "no activity by noon" alert.
  • Multi-user accounts, so more than one home can link.

Built With

Share this project:

Updates

Submission history