Mini apps
Verify the launch token
The launch token is a 60-second HS256 JWT. mMoney signs it with your mini app’s launch-token secret. Your frontend forwards it to your backend. Your backend verifies it before it trusts the user.
Claims
| Claim | Type | Meaning |
|---|---|---|
iss |
string |
Always bitt.com. |
aud |
string | Your mini-app slug. Verify this matches you. |
sub |
string | The pseudonymous user ID. Use it as your user reference. |
mini_app_id |
integer | The numeric mini-app ID. |
nonce |
string | One-shot nonce. |
iat / exp |
integer | Issued-at and expiry (unix seconds). |
Always verify four things:
-
The algorithm is
HS256. Rejectnoneand RS-prefixed algorithms. -
The issuer is
bitt.com. - The audience is exactly your slug.
- The token is not expired. A standard JWT library enforces this.
Node.js
import jwt from "jsonwebtoken";
const claims = jwt.verify(launchToken, MINI_APP_JWT_SECRET, {
algorithms: ["HS256"],
issuer: "bitt.com",
audience: "your-mini-app-slug"
});
Python
import jwt # PyJWT
claims = jwt.decode(
launch_token,
MINI_APP_JWT_SECRET,
algorithms=["HS256"],
issuer="bitt.com",
audience="your-mini-app-slug",
)
PHP (firebase/php-jwt)
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$claims = JWT::decode($launchToken, new Key($MINI_APP_JWT_SECRET, 'HS256'));
if ($claims->iss !== 'bitt.com') throw new Exception('bad iss');
if ($claims->aud !== 'your-mini-app-slug') throw new Exception('bad aud');
Elixir (Joken)
signer = Joken.Signer.create("HS256", mini_app_jwt_secret)
config = Joken.Config.default_claims(iss: "bitt.com")
case Joken.verify_and_validate(config, token, signer) do
{:ok, %{"aud" => "your-mini-app-slug"} = claims} -> {:ok, claims}
_ -> {:error, :invalid}
end
Rotation
Rotation invalidates old tokens immediately. There is no overlap window. Deploy the new secret to all backend instances before you rotate in the dashboard.