Vibes DIY
Vibes DIY / Blog
From the build log

Send your vibe's data anywhere you want

One of the first prompts a new user sent us this month was for a law practice. They wanted a form on their site for prospective clients, and they wanted every submission to land in Clio, the practice-management tool the firm already runs on. The form took one sentence. The second half was the whole point, and we had no way to do it. An app on Vibes could read from the outside world all day. It could not put a record into somebody else's tool.

Now it can, by way of Zapier.

Title card: a record leaves your app and lands in the tool you already use.
Your app keeps the form. Zapier keeps the keys to everything else.

What you can do now

Your app's backend can post a record to a Zapier Catch Hook. Zapier takes it from there into whichever tool the Zap points at: Clio, Slack, Google Sheets, HubSpot, Gmail, Notion, Trello, Airtable, a text message, a calendar. Every service Zapier connects becomes a place your app can put things, and neither you nor we had to build an integration for any of them.

It works for anything that produces a record. An intake form. An order. A volunteer signup. A bug report from the people using your app. A "somebody just RSVP'd" line in the family Slack.

Setting it up

This is about ten minutes, and most of it is in Zapier.

1. Make the Zap. In Zapier, create a new Zap and pick Webhooks by Zapier as the trigger, with the event Catch Hook. Zapier hands you a URL that starts with https://hooks.zapier.com/hooks/catch/. Copy it.

2. Give the URL to your app as a secret. On your app's Settings page, add a secret named ZAPIER_HOOK_URL and paste the address in. If you prefer a terminal:

shnpx vibes-diy secrets set ZAPIER_HOOK_URL --vibe you/your-app

A secret is seen only by your app's backend code while it runs. It never appears in the page, in a saved record, or in any version of your app, not even to you.

Get posts like this in your inbox

One email field. Real updates. No algorithm required.

3. Ask the builder. In your app's chat, say what should happen and name the secret:

When someone submits the intake form, send the submission to my Zapier hook in ZAPIER_HOOK_URL.

The builder writes the backend for you. Here is roughly what it writes, so you know what your app is doing when nobody is looking:

jsexport async function onChange(event, ctx) {
  const doc = event.doc;
  if (event.dbName !== "intake" || event.deleted || doc.sentToZapier) return;
  const res = await ctx.fetch(ctx.secrets.ZAPIER_HOOK_URL, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ name: doc.name, email: doc.email, matter: doc.matter }),
  });
  if (!res.ok) throw new Error(`zapier hook ${res.status}`);
  await ctx.db.put({ ...doc, sentToZapier: true }, { db: event.dbName });
}

Read it as a sentence: every time a record is saved in the intake database, post its fields to the hook, and once that succeeds, mark the record as sent. The first line matters: an app can have several databases, and the handler should only ever forward the one the form writes to.

4. Send one, then finish the Zap. Submit the form once. Back in Zapier, Test trigger shows your record with its fields laid out. Add the action step, Clio's Create Contact or Sheets' Create Spreadsheet Row or whatever you're aiming at, map the fields, and turn the Zap on. From then on every submission arrives on its own.

Why it is shaped this way

The URL is the password. A Catch Hook has no login. Anyone holding the address can post to it, which is exactly why it lives as a secret and never inside a record or a page. It also means we hold nothing for the far end. No Clio token, no Slack token, and honestly no idea which service you connected. If you ever want to cut the cord, delete the Zap and the address stops working.

It retries. If Zapier is having a bad minute, the send fails, and the platform tries the same record again with growing pauses, a handful of times over a few minutes. Each record is marked as sent only after Zapier accepts it, so a failed send is retried and a finished one is left alone. There is one narrow gap: if Zapier accepts the record and the mark itself fails to save, the retry sends it again. If a duplicate would matter on the far end, include the record's id in the payload so the Zap can skip one it has seen. A record that never gets through shows up in your app's logs, so it is a thing you can see rather than a thing you wonder about.

It is the last door, not the first. If what you want is to read something, a feed, a public API, a page, your app can already do that directly. If you want to keep records for yourself, they are already in the app, on every device you sign in on. Zapier is for the moment a record has to end up inside a tool that somebody else runs. The builder knows this, and it will not reach for a hook unless you ask for one.

Back to the intake form

The lawyer's app is a form, a backend that posts each submission, and a Zap that creates the contact in Clio. The person filling out the form never sees any of it. The person running the practice opens Clio the way they always have, and the new name is there.

That is the shape we like best: your app does the part that is yours, the tools you already pay for keep doing their part, and the record makes the trip between them without you carrying it.

The mechanics live on the docs page for your app's backend, which is also where the other two ways an app can reach outside itself are explained.

Make the form

Describe the form. Tell the builder where the submissions should go. The Zap is the only other thing you make.

Start building →

Enjoyed this? Get the next post by email

One email field. Real updates. No algorithm required.

Prefer a feed? RSS · Atom