HunchwireDocs hunchwire.com Sign in

Guides

Let people in

Invite people from your waitlist into your own product. Only people you invite get in.

View as Markdown

Use your agent. Paste this into it:

Read hunchwire.com/docs/let-people-in and set up invites into my app.
On this page Let people in

Let people in

Invite people into the founder's own product, so only invited people get in. It works the same whether the waitlist is on hosted pages or the founder's own site.

Hunchwire checks the invite, then sends the person to the project's address with ?hw_token=…. The token is a 10-minute HS256 JWT signed with the project's secret. It carries email (or phone, for an invite to a number), project (the project's embed_key), invite_id, iat and exp. The founder's app verifies it locally with any JWT library, and signs up only that email or number.

Invites are not referrals: they add no one to the line and move no one.

  1. The founder sends the link. The invite is invited.
  2. The person opens it. Hunchwire sends them on with a fresh token, and the invite is clicked.
  3. They can open it again as often as they need, say if your app was down or they stopped halfway. Each open makes a new 10-minute token.
  4. Your product reports that they joined, with POST /v1/people/:id/joined_product or POST /v1/invites/redeem (see API). The invite is joined, and the link stops working.
  5. A link nobody joins with expires after 14 days.

Every token carries the invite's email, or phone for an invite to a number. Your app signs up only that contact, so a forwarded link can't let anyone else in. Report each join, or the link keeps working until it expires.

The email in a token is the address the person typed. Confirm it in your sign-up, as you would for anyone. A phone is the number they signed up with: it's proven once they text you from it. Invites also come from friends' links, when friends get access (see Referrals).

GET /projects/:id/setup/site/let-in Needs analytics

Get the setup.

Details
  • address: where invites send people, or null while it's off.
  • secret_set: whether the project has a secret.
  • The secret itself is for the founder only. Ask them to copy it from Setup › Connect › Let people in into the app's environment, such as HUNCHWIRE_SECRET.
GET /projects/:id/setup/site/let-in
curl https://app.hunchwire.com/projects/:id/setup/site/let-in \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN"

PATCH /projects/:id/setup/site/let-in Needs manage

Turn it on, or change the address.

Details
  • The address must be https. http://localhost works for testing.
  • The first address makes the secret.
  • An empty address turns it off, and every invite link stops working.
PATCH /projects/:id/setup/site/let-in
curl -X PATCH https://app.hunchwire.com/projects/:id/setup/site/let-in \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"let_in": {"address": "https://yourapp.com/join"}}'

POST /projects/:id/setup/site/let-in/rotate Needs manage

Make a new secret, at once. Only do this when the founder asks.

Details
  • The old secret stops working, so invites fail until the app has the new one.
POST /projects/:id/setup/site/let-in/rotate
curl -X POST https://app.hunchwire.com/projects/:id/setup/site/let-in/rotate \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN"

POST /projects/:id/setup/site/let-in/test Needs manage

Test an invite.

Show 6 details
  • Hunchwire calls the address twice: with a test token (invite_id "test", email test@example.com), and with one signed with a wrong secret.
  • You can test 10 times a minute.
  • status: the HTTP status the app replied with, or null.
  • message: the result, in a sentence for the founder.
  • test_url: with unreachable, a link with a real test token to open in a browser. Otherwise null.
  • result is one of these:
POST /projects/:id/setup/site/let-in/test
curl -X POST https://app.hunchwire.com/projects/:id/setup/site/let-in/test \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN"
result Meaning
accepted The app took the real token and turned away the wrong one.
rejected The app turned away the real token. Check the secret.
not_checking The app took both. It isn't verifying tokens.
unreachable Hunchwire can't call it, such as at localhost.

Manage invites

An invite has these fields:

  • email or phone: who it's for. The other is null.
  • state: invited, clicked (they opened it and were sent on), joined (the founder's product reported they joined) or expired.
  • url: the link, on the project's own host.
  • sent_at, clicked_at, and expires_at (14 days).
  • redeemed_at: when the product reported the join, or null.
  • wave_id: the wave it was made in, or null.
  • via: referral when a friend's link let them in, else null.

An invite works until the person joins or it expires. Opening it again gives a new token for the invite's email or phone.

GET /projects/:id/invites Needs emails

List the project's invites, newest first, at most 200.

GET /projects/:id/invites
curl https://app.hunchwire.com/projects/:id/invites \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN"

POST /projects/:id/invites Needs manage and emails

Invite anyone, on the waitlist or not.

Show 8 details
  • For a mobile number, send {"invite": {"phone": "+14165550123"}}. A number is never emailed (emailed is phone): text it the url.
  • To invite someone on the list, send {"invite": {"person": <their id>}} instead. It uses the email or number they signed up with, exactly.
  • Hunchwire never swaps one address for another. A person sent with a different email returns 422, "That email doesn't match this person."
  • Inviting the same email again returns the same invite (200) until it's used or expires. Otherwise it returns 201 with a new one.
  • Returns 422 until Let people in has an address.
  • emailed: true when the link went out by email. Someone on the list gets it once, from the founder's domain (see Email). Inviting again emails only if it never went out.
  • When emailed isn't true, it says why: not_on_list (only people who signed up can be emailed), phone (an invite to a number: text the url), removed, suppressed, unconfirmed, or an email.blocker reason such as plan. Then give the url to the founder to share by hand.
  • unconfirmed: they signed up again after being deleted. Hunchwire emails the invite once they confirm their email. Don't share it by hand.
POST /projects/:id/invites
curl -X POST https://app.hunchwire.com/projects/:id/invites \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"invite": {"email": "maya@fernwood.studio"}}'

Invite in waves

A wave lets many people in at once.

GET /projects/:id/people/waves Needs emails

List invite waves, newest first.

Show 8 details
  • Each has id, name, created_at, invited and by (who made it).
  • picked: how people were picked: top, segment or ids.
  • message: what its invite email says, or null for the default.
  • emailed_at: when its invite emails went out, or null.
  • email_held: true while its emails are held. They go out by themselves once the plan allows email and email is set up.
  • by_text: of those invited, people who signed up by number. Hunchwire never emails them, so text them their links.
  • clicked: how many opened the link. It never counts email opens.
  • Each invite's state says whether the product reported them joined.
GET /projects/:id/people/waves
curl https://app.hunchwire.com/projects/:id/people/waves \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN"

POST /projects/:id/people/waves Needs manage and emails

Invite many people at once. Only do this when the founder asks.

Show 13 details
  • top picks the first N waiting people by place, from 1 to 1000. Instead, send "segment": <id> for everyone waiting in a saved segment, whenever they joined, or "ids": [...].
  • Only waiting people get a link. Hunchwire skips removed people, people already invited, and anyone who asked to be deleted. skipped counts them as removed, invited and suppressed. Someone who signed up again after being deleted gets a link, emailed once they confirm.
  • Each link works for the email or number they signed up with, for 14 days, until they join the founder's product.
  • Returns the wave with its invites (as above), 201.
  • Returns 422 until Let people in has an address, or when no one is left to invite.
  • Hunchwire emails each person their link from the founder's domain. The email says "You're in. Welcome to …", then the wave's message, then a Get started button.
  • People who signed up by number get no email: by_text counts them, and your product texts their links.
  • message is up to 2,000 characters. Without one, the email uses a plain default.
  • emailed: how many emails went out. email_skipped: the rest, counted by reason (suppressed, unconfirmed, or an email.blocker reason). Each unconfirmed invite is emailed once that person confirms their email.
  • Hunchwire makes the links either way. Give the founder the ones that weren't emailed, or the wave's CSV.
  • On a plan without email, email_skipped is {"plan": N}. The emails are held. They go out by themselves once the founder upgrades and email is set up, within a few minutes of the domain being verified.
  • A wave made while email isn't set up, on a plan with email, is never emailed later. Share its links by hand.
  • Add "test": true to send one sample invite email to the founder who connected you instead. It's free on every plan, and nothing is made. It returns {"test": "sent", "to": "…"}.
POST /projects/:id/people/waves
curl -X POST https://app.hunchwire.com/projects/:id/people/waves \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Founding 50", "top": 50}'

GET /projects/:id/people/waves/:wave_id Needs emails

Get one wave with its invites.

Details
  • .csv returns email, link, state, clicked_at and phone for each invite. A number's email is blank.
GET /projects/:id/people/waves/:wave_id
curl https://app.hunchwire.com/projects/:id/people/waves/:wave_id \
  -H "Authorization: Bearer $HUNCHWIRE_TOKEN"

Check the token

In the founder's app, check the token like this. The Let people in screen also shows Python and Ruby.

Check the token
import jwt from &quot;jsonwebtoken&quot;;
// On your address (?hw_token=…): check it, and remember who it&#39;s for.
const { email, phone, invite_id } = jwt.verify(req.query.hw_token, process.env.HUNCHWIRE_SECRET, { algorithms: [&quot;HS256&quot;] });
req.session.invited = email || phone; // phone, like +14165550123, for an invite to a number
// On sign-up: only that email gets an account (for a number, compare the number they verified).
if (form.email.trim().toLowerCase() !== req.session.invited) return refuse(`This invite is for ${req.session.invited}. Use that email, or ask for an invite for this one.`);

Made from skill.md, the file agents read. Add .md to any page's address for its Markdown.