Inspiration

Production breaks when you're not at a desk.

Every service I deploy to already exposes a webhook — a Vercel deploy hook, a GitHub workflow dispatch, a Netlify build trigger. Firing one is a single HTTP request. But actually doing it means finding a laptop, opening a terminal, and digging out the right URL.

That gap — between knowing exactly what to run and being able to run it — is where a five-minute outage becomes a thirty-minute one.

What it does

Hangar turns the webhooks that run your infrastructure into labelled buttons.

You group them by service, one screen per place you deploy to, and each service holds buttons carrying your own icons, methods, headers and bodies. Press one and the request goes straight from your iPhone to your endpoint. Every button shows the host it calls, so nothing is a mystery box.

The point is that you never have to open the app. Buttons live on home screen widgets, Lock Screen accessories, and behind Siri. A Live Activity tracks a running command in the Dynamic Island — and all of that is free, because an app whose pitch is "fire it without opening the app" cannot put that behind a paywall.

Every press is saved with its status code, duration and response. Dangerous buttons wear a caution mark and ask before they fire. Sequences chain several buttons with a wait between each, so "roll back, wait thirty seconds, health check, notify" becomes one press.

How we built it

SwiftUI throughout, on a strict UI → Controller → Repository → Service → Model chain.

Firebase Authentication, Cloud Firestore and Cloud Functions on the backend. RevenueCat for subscriptions. ActivityKit, WidgetKit and App Intents for everything outside the app.

Webhook URLs, headers and bodies are encrypted with AES-GCM by a Cloud Function before storage and decrypted only at the moment a command fires. The host is stored separately as non-secret metadata so an encrypted button can still show where it goes. Widgets and Siri read a snapshot from a shared App Group container, so they render without a network round trip.

The design system is a two-palette theme where colour carries meaning rather than decoration: grey does the structural work, blue marks anything you can press, orange marks anything in flight or dangerous, and green and red are reserved for a command's actual outcome. Buttons are moulded keys seated in a recessed well, with a lit top rim and a shadowed bottom edge. The press cues are synthesized waveforms rather than stock sounds, played through an ambient audio session so firing a command never interrupts your music.

Challenges we ran into

A silent security downgrade. The encrypting Cloud Function enforces a free-tier quota. When it refused, the client caught the error and stored the webhook in plain text instead — auth tokens included — with no warning to anyone. Exceeding a quota quietly turned encryption off. The fix was teaching the fallback to tell "there is no backend" apart from "the backend said no", and only falling back for the former.

A claim in a privacy policy is a spec. That policy said webhook secrets were encrypted at rest. They weren't. The encrypting function existed and was simply never called. Wiring it up exposed a second bug hiding behind the first: with encryption on, every trigger would have tried to POST to a string beginning enc:v1:, because the fire path read the stored ciphertext directly instead of fetching the decrypted secret. Encryption and decryption had to land in the same commit or the app would have broken the moment it became secure.

Renaming a button deleted its URL. Saving metadata wrote the whole record with the URL field blanked, and a merge write pushes that empty value over the real one. Fixing a typo in a label silently destroyed the webhook behind it.

Siri appeared to work and never did. The command list conformed to EntityQuery, which only resolves entities by ID. Matching spoken words to a command requires EntityStringQuery. The shortcut showed up in the Shortcuts app, so everything looked correct, and voice invocation could never succeed.

Firestore was serving from cache. Every read was being refused by security rules, but the SDK answers from its local cache and queues writes optimistically — so the app looked healthy while nothing reached the server at all.

Accomplishments that we're proud of

Colour that does real work. A button carries its last outcome on its icon and its edge, so a screen of eight can be read at a glance — which one faulted last night, which has never run. Dangerous ones are marked before you press them, which matters in an app built to fire production commands from a Lock Screen at 2am.

Onboarding that demonstrates rather than decorates. Three animated scenes show a key being pressed, a request travelling to an endpoint and a green result returning; the three surfaces the buttons live on; and a log filling up — including a row that fails with a 500, because a tool you would trust with a rollback should show you failures in its own sales pitch.

A demo that works with zero setup and doesn't lie. Four real buttons pointed at public test endpoints — one succeeds, one echoes a payload back, one runs for three seconds so you see the in-flight state, one always fails.

Accessibility treated as craft: type scales with Dynamic Type and holds its layout at the largest sizes, VoiceOver announces real labels instead of SF Symbol names, and Reduce Motion suppresses travel while keeping feedback.

What we learned

I built the whole thing and still couldn't explain it. I showed it to colleagues and to my CEO and every one of them said it was difficult. I blamed my pitch until I looked at the words: deck, command pad, active surface, hangar, flight record, rails, operator, callsign, flows, execution profiles. Not one was a term a developer already knows. The app was written in a private language, and you cannot explain something whose vocabulary you invented. Renaming everything to Services, Buttons, History and Sequences did more for the product than any feature I shipped.

Swapping a hue does not fix a palette. I tried four — steel, industrial orange, airfield blue, and the original brass — and none of them helped, because one accent was doing every job at once: labels, buttons, icons, borders, badges. Colour only stopped looking generic when it started carrying information.

RevenueCat has three separate concepts and they are not interchangeable. Products define what exists, Entitlements define what a purchase unlocks, and Offerings define what the app may show. The SDK reads only the current offering — one that exists but isn't marked Current is invisible.

The Simulator cannot prove the things that matter most. Widgets, Live Activities, Siri and Sign in with Apple have never run there, so "it works on the Simulator" is a claim about a subset of the app.

What's next for Hangar: Deploy Control

Device testing for widgets, Live Activities and Siri, then the App Store.

After that: a notification when a command fails while you're in another app, so a failed rollback reaches you immediately; cloud-side execution so sequences run without the app open; and team sync tested properly across accounts.

Built With

  • activitykit
  • app-intents
  • cloud-functions
  • cryptokit
  • firebase
  • firestore
  • revenuecat
  • swift
  • swiftui
  • widgetkit
Share this project:

Updates

posted an update —

Hangar 1.1 is live.

Four weeks on the App Store. The community asked for two things: a ping when a command dies in the background, and flows that keep running after you lock the phone. Both shipped.

Fail ping hook returns 500 while Hangar is in your pocket, Lock Screen, not a surprise in the morning. Flows after lock Hangar Cloud runs the chain. A failed step stops the rest and pings you. That is the 11pm pad. Press, lock, get told if it dies.

Hangar: Deploy Control https://apps.apple.com/app/id6806856067 Allow notifications after you update.

Log in or sign up for Devpost to join the conversation.

posted an update —

2 weeks since Hangar went live for Shipaton
20 organic downloads 2K+ App Store impressions Ranked #21 for “Vercel”

Small numbers but watching something I built get discovered completely organically feels pretty damn good.

Log in or sign up for Devpost to join the conversation.

Submission history