All posts
8 min read

Building Obeagle

I built Omegle, but for dogs.

It started with me joking with friends about ridiculous "Uber for X, Tinder for Y" ideas, and I thought it would be hilarious to build. When I saw that the domain Obeagle.com was available, I knew I had to do it.

I built the first functional version of Obeagle in less than a day. Essentially like Omegle, but you had to show a dog to the camera, you could get matched with someone else and their dog, talk for a bit, then move on to the next one.

I showed it to a bunch of people, and they thought it was the funniest thing ever, so I figured why not use this as an opportunity to showcase my product design and development skills. It's a lot more memorable than some random B2B SaaS idea.

Making infra costs manageable

I used Daily.co for the MVP. That was the right decision because I just wanted to ship something fast, and Daily let me get the video part working without spending days on WebRTC.

The issue was that the costs could get pretty insane if people actually used it, so I figured I'd just build my own infrastructure and save myself the possible risk.

I ended up moving normal calls to peer-to-peer WebRTC, so the video goes directly between the two browsers. Some connections can't do that reliably, especially on mobile or weird networks, so those fall back to Cloudflare TURN, which is still cheaper than Daily. Daily is still there as a fallback that I can turn on if all else fails for some reason.

That made the costs much more manageable, but it also meant that I'd have to test calls between an iPhone on cellular and a laptop on Wi-Fi, dealing with cameras flipping halfway through a call, and trying to recover when someone's network drops for a few seconds. The first version of Obeagle did none of that particularly well, but I got it working after a lot of testing + trial and error.

Moderation challenges

Moderation would be really easy if costs didn't matter. For video calls, you're not checking 100% of the frames, but you need to sample enough frames to catch unwanted NSFW activity and keep the experience safe and friendly.

The overkill version would be to send frames from every call to a strong moderation service constantly, which seriously adds up on a two-way call and opens up the door to bad actors draining your budget.

I started with looking into lightweight open-source models that could run locally on a mobile device. NSFWJS was one of the first options that I considered — it's a JavaScript library that identifies inappropriate or explicit images directly in the user's browser using TensorFlow.js.

Sounds great, right?

Well the problem with many of these moderation models, especially the lightweight ones, is that the easiest way to train them is using... "professional" content. That training material is quite different from a half-exposed flasher in a dimly-lit room using a front-facing mobile camera.

It was hard to find a confidence threshold for the model that could reliably flag most explicit "user-generated content" without a really high false-positive rate. If you set the threshold too high, it's pointless. If it's too low, the platform becomes unusable. I explored the possibility of using NSFWJS or another local model as the gate that determines which frames get sent to an external image moderation API, but given the poor reliability of the model for this use-case, I realized NSFWJS wasn't going to work.

I tried adjusting the threshold and ran several evals with frames of myself or friends using the platform safely — showing their arms, their dogs, living room, and whatever you can typically expect from users — and compared that with NSFW images of users on Omegle, etc. from Google images (I couldn't think of a better way to do this, and I wasn't going to use myself as the nsfw eval lol.)

Anyway, some actual NSFW test images scored lower than completely normal frames. If I raised the threshold enough to stop ending calls because somebody waved at the camera, it missed too much. If I lowered it, the product became unusable.

I tried other open-source models. I tried one on a Render server. Somehow that one was worse. This went on for a bit. So I took a step back and took another look at the moderation API options. Amazon Rekognition and OpenAI omni were the two main options.

I ran another small evaluation set with those models based on the kinds of frames Obeagle would likely have to deal with: bad lighting, motion blur, a hand over the camera, somebody sitting too close, a wall or couch that happens to be skin-colored, a couch, a dog, someone's leg in the frame while they show their dog, etc.

OpenAI's moderation model (openai-omni) was the first hosted option that worked well enough on tests. The endpoint was free, but I initially dismissed it because it's pretty rate-limited. I did the math, and even with fewer, more strategic, and spaced-out checks, it would only support a very, very small number of users. Checking both sides of a call can really add up.

But then... I found a surprisingly simple solution to this problem. I found out that purchasing a small amount of OpenAI API credits raised my account's rate limit significantly, and actually made it feasible as a primary moderation model with decent scale.

Amazon Rekognition performed well in the evals, so I decided to use it as a fallback option in case I ever hit OpenAI's per-minute rate limit, or in case of an outage with the OpenAI endpoint.

The current design has a few layers to it, which are designed primarily to catch and stop users who intend on exposing themselves to others (i.e. "flashers"). It's also designed to catch or prevent users from tricking or abusing the moderation systems, either to avoid detection or try to get other users banned if they were to try and "fake" the video feed they're sending or receiving.

There are strategically timed server-side checks, user reports, an admin review portal, and more. I can turn the different parts on separately while I test them.

I'm leaving out the exact timing, thresholds, and enforcement rules for obvious reasons.

The other part is capacity. When there are fewer calls, Obeagle can afford to run extra checks, which is especially important in its infancy. When traffic scales, those extra checks are reduced, but there is a baseline that stays in place. To ensure that costs could be manageable and predictable, I have an internal hard-cap on concurrent users — it's a surprisingly high number, and if it ever reaches that, users enter a queue to get on.

I also had to decide where to draw the line on privacy vs. safety. My intention was for the experience to be as privacy-respecting as possible, without sacrificing safety. Any evidence connected to reports or confirmed moderation actions can be stored privately (and securely) for review, but normal webcam frames are not tracked or saved. I also don't use session replay, both for privacy and practical constraints.

Setting a cap

I was struggling to figure out how to avoid outages if this were to ever hit high concurrent users at some point. Eventually, I just realized I should set a cap, worry about that problem if it ever comes up, and let FOMO run its course.

I don't love the idea of turning users away, but pretending an early product has infinite capacity would be worse.

Solving the empty-room problem with TikTok for dogs

Video chatting products are boring and pointless if no one else is on. So I added something I call DoggiTV, which is like TikTok or YouTube shorts for dogs.

I pulled a bunch of late 2000s / early 2010s videos from YouTube of people showing their dogs on webcams, so it feels a little more "alive" when no one's online or you're between calls.

You can also invite your friends to chat with their dogs and get matched with them.

Design perfectionism

I put a lot of thought into design, and there was a ton of iteration here.

There are many screens, many user states, interactions, animations, room for things to go wrong. And if you're building and designing with LLMs, you also have to fight back against laziness and drift. I wanted it to feel like a cohesive brand experience, and I think I mostly achieved that.

I worked with GPT Sol, Kimi K3, Claude Opus 4.7 or 4.8, and Fable 5 at different points - ultimately, I learned that a model wasn't going to magically spit out what I wanted, I stopped getting lazy, and started to more clearly define (to myself) what I wanted. Once I had a clearer idea and put in the work, this got much easier.

I found it helpful to use paper.design as a design canvas where I could see all of the different screens without having to click through the product every time. This helped ensure design alignment across the application and be able to preview design iterations before they went into staging or production.

Still deciding when to require a sign-up

Accounts would make bans more durable, prevent bad actors, and make it easier to reach out to people who sign-up if I want to launch scheduled virtual events where people get on around the same time.

On the flip side, they add friction, which isn't a good thing at this stage. I think adding a hard gate with sign-up at the beginning was a poor move. Ideally, I'd like to let people get on a few calls before requiring them to sign-up, but I just wanted to ship it at this point and there was a lot of technical risk with building this way.

Vibe coding

Imagine an engineering and design team spending weeks or months working on something like this.

AI also made it very easy to build plausible things that were wrong. I built multiple moderation approaches before I had one I trusted enough to keep. I went through a lot of design versions that looked finished in a screenshot and felt wrong the second I used them on my phone.

The useful part was being able to try those ideas quickly. The less fun part was that I still had to figure out when the result was bad, why it was bad, and whether the next version was actually better.

Obeagle has not been tested at meaningful scale yet. I'm not going to pretend it has. But the things I worry about now are different from the things I worried about after the one-day MVP, which is probably the clearest way I can describe the progress.