What is still missing from Rekey, and whether it is coming
Blog RoadmapIf you are deciding whether to build on Rekey this month, the useful question is what is still missing, whether it is coming, and what you do in the meantime. This post answers that for every open item that affects a developer, grouped by theme. Each one says what exists today, what changes, and how far along it is.
The status words are deliberate. In progress means part of it has shipped and the rest is being built. Planned means the design is settled and the work is queued for the next releases. Under discussion means the problem is agreed and the design is not, so your input can still change it. There are no dates here, and no version numbers beyond the next releases.
Where money and access can drift
The payment provider is the source of truth for money, and your Rekey database is the source of truth for what a customer may do. The open items here are the places where those two can drift apart without anyone noticing.
Refund and chargeback revocation. Planned. Today a refund at the provider is recorded: the payment moves to PARTIALLY_REFUNDED or REFUNDED, a payment.refunded webhook fires, and the panel and the portal show how much came back. What Rekey does not do is take back what that payment bought. A refunded credit pack stays spendable, and a refunded subscription stays active until you cancel it. The design is settled: a full refund will revoke credits, licences and the subscription, a partial refund will revoke nothing, and a dispute will wait for the provider's verdict instead of acting on the opening event. Until it lands, the payment.refunded webhook tells you which payment and how much, and the credits come back with a negative adjustment on the user's Credits page in the panel, or on the operator route behind it:
POST /api/v1/tenant/applications/<app>/end-users/<user>/credits/grant
{ "amount": -10000, "reason": "ADJUST", "idempotencyKey": "refund:pay_1" }The adjustment is audited like any other grant. That route takes a panel session today, which is one reason the next theme exists. The event shapes are in the webhooks reference.
Reconciliation against the processor. Under discussion. Every change to a subscription arrives as a provider webhook. If the webhooks stop, because a secret was rotated at the provider or an endpoint was disabled after too many failures, the local record keeps the status it had. A customer who cancelled at the provider keeps reading ACTIVE. Nothing today asks the provider whether the record is still true. What exists is the local half: the reconciliation report compares the billing log with the payments, refunds and subscriptions of a period and lists where they disagree, and a charge that matches no checkout is filed as an unapplied case instead of being dropped. Run the report on a schedule and treat a non-zero count as a page:
rekey billing reconciliation --app <id> --json # last 7 days by default
The job that would ask each provider for its own view is specified and not built, and the open question is this: it may repair a record, but it must never take a customer's access away from a local timestamp, because every provider keeps a customer entitled through its retry window. The report is documented in the billing log guide.
Scheduled term expiry. Planned. 2.2.0 made trials sellable, one per buyer per Application, and a trial or a granted term does expire. It expires lazily, though: the status flips when something reads the subscription, and no event announces it. Until the sweep exists, listen for subscription.trial_will_end, which fires three days before trialEndsAt, and read entitlements at request time rather than caching them. The trial rules are in selling a free trial, once per buyer.
First-period offers on every provider. Under discussion. A coupon today takes money off the first invoice. A discount that lasts three months is native on Stripe, needs a plan variant on PayPal, and cannot be created outside the dashboard on Razorpay. The direction being discussed is for Rekey to own the offer and ask a provider only to charge once and to start recurring billing on a date, which makes a card-required trial and a first-month discount the same two calls everywhere. For now, a permanent discount is just a second plan at a lower price, and a first-period discount is a coupon.
Additive add-ons. Under discussion. A user can hold several subscriptions in one Application, and the two entitlement kinds combine differently. Included usage quotas add up: two plans that each include 1,000 API calls give the subscriber 2,000. A numeric feature value takes the higher of the two: seats: 1 plus seats: 5 is five seats. So “buy two more seats” cannot be sold as a second subscription today. Sell tiers instead: Standard with seats: 1, Team with seats: 5, and an upgrade moves the buyer up. For one customer who needs an odd number, a per-subscription entitlement override sets the value directly from the panel. Summing feature values would need every entitlement to say how it combines, which is a schema change, so it waits for a real buyer who wants units rather than tiers.
Driving Rekey without a browser
An operator has three ways in besides the panel: a personal access token (rp_op_…) on the REST routes, the operator MCP server that an agent connects to, and the CLI. The gap is that none of them reaches everything the panel does.
Token access to every operator route. In progress. Until 2.2.1 a personal access token could list Applications, list keys and mint a key, and nothing else: every other tenant route wanted a panel session. 2.2.1 opened the onboarding question routes and the contact list routes to tokens, with the route's own scope required, and added twelve operator MCP tools for the same (set_profile_schema, create_contact_list, unsubscribe_list_member and the rest). What is left is the same guard on the remaining tenant routes, an opt-in admin token scope so a token can do what the admin-tier MCP tools do, and MCP tools for end users, bans, webhooks, email and support. A few routes will stay session-only on purpose: minting tokens, passwords, MFA, passkeys and the end-user export. Today, the widest headless surface is the operator MCP server over OAuth from an MCP client; the MCP guide shows how to connect one.
CLI and standalone package parity. Under discussion. @rekey.dev/cli covers bootstrap, Applications, plans and prices, coupons, the billing log and its reports, payments, refunds, lists and user analytics. It has no commands for devices, user imports, licences, API keys or operator scopes. The @rekey.dev/mcp package is a smaller admin-key server than the hosted operator MCP. Whether both grow to parity, or the package is documented as the small server it is and points at the hosted one, is a product call that has not been made. The decision is easier with a reason attached, so if you need a command, say which.
| Surface | Reaches today |
|---|---|
rp_op_ | Applications and keys, onboarding questions, contact lists, with the route's own scope. |
| Operator MCP | Billing reads and writes, analytics, the billing log, lists and profile fields, admin-tier tools over OAuth only. |
| CLI | init, doctor, apps, plans and prices, coupons, billing reports, payments, refunds, lists, analytics. |
| Panel | Everything. |
Checkout
Card fields on the page for PayPal. Under discussion. The Rekey checkout page now takes payment through Stripe, PayPal and Razorpay, for subscriptions and one-time purchases, under your name, logo and colours. With Stripe the card inputs are the Payment Element inside the page. With PayPal the buyer still goes through PayPal's buttons and window, and with Razorpay through Razorpay's window. The request is a card form inside the page for every provider, with the inputs hosted by the provider so no card number ever reaches a Rekey server, yours or ours. The 3-D Secure step-up is the part that differs per provider and has to be prototyped before any component is promised. Today, switch the page on per Application and per mode under Billing, Checkout page; the Test column first, with sandbox credentials, is the way to see what your buyers will see. The rollout is described in the 2.2 release notes.
Pay first, sign up after. Under discussion. A checkout needs a signed-in user: the subscription row is keyed on the end user, and the activation webhook finds the buyer from the id stamped on the session. So every starter kit asks for an account before showing a price. The reverse flow, pick a plan, pay, then create the account, needs a paid-but-unclaimed subscription and a claim step, and the claim step is where the care goes. A claim token in a URL is worth money, and binding on an email the browser supplies would let anyone claim a stranger's purchase. Two cases also need answers before a line is written: a buyer who pays and never returns, and a buyer who pays with one address and signs up with another. In the meantime, make the account step cheap. A magic-link sign-up is one field, and isNewUser on the result sends a first-timer straight to checkout:
// @rekey.dev/node, on your server
await rekey.auth.requestMagicLink({ email });
// on the page the link lands on
const result = await rekey.auth.verifyMagicLink({ token });
if (result.mfaRequired) return redirect('/mfa');
redirect(result.isNewUser ? `/checkout?plan=${plan}` : '/dashboard');The routing pattern is in sending new users to onboarding.
Server-side conversion events. Under discussion. Browser-side purchase tracking under-reports, and it under-reports unevenly by traffic source, which corrupts the cost per acquisition you compare channels on. Rekey holds the authoritative event, the provider webhook that activated the subscription. Forwarding it to GA4's Measurement Protocol from the server is delivery work Rekey already has (the outbound webhook queue, with retries) plus one hard decision: how the server event is joined to the browser session that started the checkout. That join decides the SDK surface, so it is settled first. Today, your own webhook handler is that adapter: subscribe to subscription.activated and payment.succeeded, verify the signature, and post to your analytics destination from there.
Enterprise sign-in
These are the items a procurement form asks about. None of them has started, and that is said here so you do not plan around them.
- SAML single sign-on. Planned, not started. There is no SAML code in Rekey. The first version will be SP-initiated SAML for the end users of one Application, one identity provider connection per Application, mapped onto the identity-linking flow that social sign-in already uses. Today, end users sign in with a password, a magic link, a passkey, or an OAuth provider you configure. If your customer's requirement is “our users sign in through your app” rather than “through our identity provider”, Rekey can be the OpenID Connect provider for other services already; see your Application as an OIDC provider.
- SCIM provisioning. Planned, after SAML. No automated joiners and leavers from an identity provider. Today, create and delete end users from your server with the secret key, and ban an account to deprovision it without deleting its record. The routes are in the API reference.
- Invoice PDFs. Planned. Rekey has no invoice entity, so there is nothing to render. The hosted portal shows a customer's payments with how much of each was refunded and links to the provider's receipt when the provider sent one. The legal fields an invoice needs, your registered name, address and tax id, are the input the design waits on. The portal's current surface is in the portal guide.
Operating it
Zero-downtime self-host deploys. Planned. The self-host compose runs one API container behind Traefik. A deploy replaces it, and Docker Compose alone does not hand over: the old container stops before the new one is accepting connections, so for a short window every request, /.well-known/jwks.json included, gets a 404 from the proxy rather than an answer from the API. It recovers by itself in under a minute. The container has a liveness check on /health/live and the API serves /health/ready for a load balancer to gate on; the change is to make the handover wait for it. Until then, deploy at a quiet hour and have any service that caches the JWKS keep the last good key set through a failed refresh. The compose layout is in self-hosting Rekey.
WordPress and Payload CMS plugins. Planned. The repositories exist under the public organization and hold a licence and a README, no code yet. The shape is the same for both: sign in through Rekey, and gate content on the entitlement the payment webhook wrote. Today, any server that can verify a JWT can do the same by hand: end-user tokens can be signed with RS256 and checked against /.well-known/jwks.json, and @rekey.dev/node reads entitlements. The pieces are in the SDK guide.
What is not on this list
Nothing above blocks sign-up, sign-in, organizations, checkout or metering. Those work today. The Next.js starter kit has sign-up, sign-in, checkout and metered credits wired against an Application you can create now.
How to change the order
The order above is a best estimate of what blocks the most people. A concrete case moves an item more than anything else: which provider, which flow, and what you had to do by hand. Two places take it.
- Open or vote on an issue in the public repository. Pay-first checkout, the card form on the page and server-side conversion events are already open there; add your case to the thread instead of opening a second one.
- Say it on Discord. A half-formed need is fine; the design questions above are exactly the kind of thing a real example settles.
When an item moves a column, this post gets an update line at the top, and the changelog carries the detail.
Was this page helpful?
