Inkwave Help

Reader endpoints

The public links and forms on your publication's site that you can build on, such as embedded subscribe forms, custom sign-up links and one-click unsubscribe.

What this page covers

Readers reach your publication through a few public addresses on your site. This page lists the ones that are safe to build on: embedding a subscribe form, sending readers to a sign-up link of your own, one-click unsubscribe, and the poll links in your emails.

In the examples, https://<your-domain> (news.example.com inside code blocks) is your publication's address: your verified custom domain, or your Inkwave subdomain if you don't have one.

Anything not listed here is internal. See "Routes that aren't stable" at the end.

Embedding a subscribe form

Each subscribe form has its own page at https://<your-domain>/f/{formId}, made to sit inside an iframe on another site. Copy the snippet from the form's embed panel under Tools, Subscribe forms. It has two parts.

First, an iframe whose src is the form's page, with your address and the form's id filled in. It also carries a data-inkwave-form attribute set to the form's id, which the listener uses to find it, a full width and a fixed height of 360 pixels, plus title, scrolling, loading and a border-free style.

Then a short listener in a script tag, which resizes the iframe to fit the form:

window.addEventListener("message", function (e) {
  var d = e.data;
  if (!d || d.source !== "inkwave-subscribe-form" || d.type !== "resize") return;
  var f = document.querySelector('iframe[data-inkwave-form="' + d.formId + '"]');
  if (f && d.height) f.style.height = d.height + "px";
});

The listener is optional. Without it the iframe keeps its fixed height and the form still works.

The resize message

The form tells the page around it how tall it is, using postMessage. It sends the message once when it loads and again whenever its height changes:

{
  "source": "inkwave-subscribe-form",
  "type": "resize",
  "formId": "{formId}",
  "height": 412
}

height is in pixels. The message is posted with the target origin *, so check source and type before you act on it, as the snippet does. Match formId to the right iframe when a page holds more than one form.

Referral and recommendation codes

Add ?ref= (a reader's referral code) or ?rec= (a recommendation id) to the iframe's src, and the form passes it on with the sign-up.

What happens after a reader submits

The form sends the reader to /magic (see below) with form_id set. Every result then comes back to the form instead of your site's pages, as https://<your-domain>/f/{formId}?state=...:

stateMeaning
successThe sign-up went through. Shows the form's success message.
pendingDouble opt-in is on, and the reader needs to confirm by email.
awaiting_approvalYour publication is private and needs approval, and double opt-in is off, so this is a request to join.
errorSomething stopped the sign-up. message carries the reason.

If the form has a success redirect URL, a successful sign-up goes there instead, with email added to the URL. If the form is set to remove the email from the redirect, subscription_id is added instead, or nothing when there is no id to give (for example, the reader was already subscribed).

To sign readers up from your own form or link, send them to:

GET https://news.example.com/magic?email=&redirect_to=&ref=&rec=&form_id=

It is a plain GET, so an ordinary HTML form with method="get" and action="https://<your-domain>/magic" works, with no JavaScript. There is no POST version.

ParameterRequiredWhat it does
emailYesThe reader's email address.
redirect_toNoWhere to send the reader after a successful sign-up. Must be on your own site (see below).
refNoA reader's referral code, so the referrer gets credit.
recNoA recommendation id, when the reader came from another publication's recommendation.
form_idNoA subscribe form's id. Results go back to that form (see above) and redirect_to is not used.

If ref or rec is missing, the codes saved from the reader's first visit are used instead. A visitor who lands on any page of your site with ?ref= or ?rec= keeps that code in a cookie for 60 days.

Outcomes

Every outcome below is a 307 redirect. The checks run in this order, and the first that applies decides. Without form_id:

SituationWhere the reader goes
No emailYour home page with ?auth_error=Missing email
The domain isn't a publicationYour home page with ?auth_error=Publication not found
The publication is suspendedYour home page with an auth_error saying it isn't accepting new subscribers
The publication is private, without approvalYour home page with an auth_error saying it isn't accepting new subscribers
Double opt-in is on (also on a private publication with approval on)/magic/pending, and a confirmation email is sent
Double opt-in is on, but the confirmation email request failsYour home page with ?auth_error=Failed to send confirmation email
The publication is private, with approval on, and double opt-in is off/magic/awaiting-approval
Double opt-in is offredirect_to if it is allowed, otherwise /magic/success

If the sign-up itself fails on Inkwave's side (an error from the subscribe call), /magic answers with a 500 error instead of a redirect.

With form_id, the same situations return to the form with the matching state, as described above.

Double opt-in

When double opt-in is on, /magic doesn't subscribe the reader. It sends a confirmation email, and the reader is only added once they click the link in it. That link signs them in to your site too. It carries redirect_to and form_id along, so after confirming the reader lands where the original request asked: the form, redirect_to, or your signup flow if you have one.

The confirmation link expires after 30 minutes and works once.

Readers always get the same answer whether or not an email was really sent, so the response never reveals who is already subscribed. For example, someone already subscribed gets no email but sees the same page.

Where redirect_to may point

redirect_to is only followed when it stays on your publication's own host. It can be:

  • a path on your site, such as /welcome
  • a full https:// (or http://) address on the same domain the request came in on

Anything else, such as another domain, an address with a port or a login part, or a path starting with //, is ignored and the reader goes to the default page instead. The same rule applies again when a reader confirms by email.

One-click unsubscribe

This part is mainly for deliverability teams.

Every newsletter and automation email Inkwave sends to a subscriber carries the two headers from RFC 8058:

  • List-Unsubscribe: <https://<your-domain>/preference/{token}/one-click>
  • List-Unsubscribe-Post: List-Unsubscribe=One-Click

Mail providers such as Gmail and Yahoo use them to show their own unsubscribe button. When a reader uses it, the provider calls the address directly:

POST https://news.example.com/preference/{token}/one-click
Content-Type: application/x-www-form-urlencoded

List-Unsubscribe=One-Click
  • The body must contain List-Unsubscribe=One-Click, or the request is refused with 400.
  • With that body, the reader is unsubscribed from your publication and the answer is 200 with no body. This is the same unsubscribe as the link in the email footer, so counts, billing and the subscription.deleted webhook all follow.
  • A reader who has already left also gets 200, and nothing changes.
  • An unknown token also gets 200, so the address can't be used to test which tokens exist.
  • A GET to the same address never unsubscribes anyone. It redirects to the preference centre at /preference/{token}, where the reader chooses. This protects readers from mail scanners that open every link in a message.

The token belongs to one subscriber of one publication, and anyone holding it can unsubscribe that person. Treat these URLs as private and don't log or share them.

Sign-in and confirmation emails don't carry these headers, because there is nothing to unsubscribe from.

A poll in a newsletter email shows each answer as a link. The links point at Inkwave's own address rather than your site, and look like:

GET .../polls/public/redirect?poll_id={pollId}&option_id={optionId}&sub={subscriberId}

sub identifies the reader the email was sent to, so each copy of the email has its own links. When a reader clicks an answer:

  • the vote is recorded for them, and they see a page that says "Thanks for voting! Here are the results:" with the percentage for each answer
  • if they have already answered, the page says "You've already responded to this poll."
  • if the link is broken or no longer matches a subscriber of your publication, they see a short error page instead

Each reader can answer once. Someone the email was forwarded to votes as the original reader. Each vote sends the poll.response_submitted webhook.

A poll on your website works differently, through your site's own script, and isn't meant to be called from elsewhere.

Rate limits

Sign-in and confirmation emails are limited to one per email address per 60 seconds, shared between the two kinds. A request inside that minute gets the same answer as any other, but no email is sent, and the link already sent keeps working.

Your publication also has an hourly cap on these emails (30 an hour), with platform-wide caps above it. Over a cap, the request is answered the same way and nothing is sent.

Routes that aren't stable

These are used by Inkwave itself and are deliberately not documented. They can change or disappear without notice, so don't build on them:

  • the sign-in link and its check (/auth/verify, and the backend's verify-sign-in)
  • the confirmation link target (/magic/confirm): use /magic and let the email carry the reader there
  • reader reply notifications (reader-reply)
  • tracking internals: /t/click, /t/pixel, /t/collect, /t/search and /t/poll
  • callbacks from the email provider (Resend) and from Stripe

On this page