Hosting
Where the game lives, the rules its URL must pass, and the checks uloggd runs against it.
uloggd frames the URL you register and nothing else. The game, its assets and its APIs stay on your hosting; uloggd never fetches them for players.
One origin per game
Give every game its own origin, in practice its own subdomain:
https://orbit.example.com, not https://example.com/orbit.
Two games on one origin would share localStorage, IndexedDB, cookies and service workers inside uloggd: one could read the other's tokens, and approving one would vouch for the other. So an origin belongs to one app.
- An app claims an origin the first time a check of one of its releases passes the proof of control below.
- A release on an origin another app holds is refused with
origin_in_use. - You may hand an origin to another of your own apps, but only while no release on it is submitted, approved or live.
- The origins of a removed app stay reserved to you, and are never given to anyone else.
The URL rules
A release's entry URL must pass every rule below. The portal says which one
failed: url_invalid, or url_denied for the denylist and the names uloggd
never frames. A url_denied about an international name or about the network
also carries detail, which says why (see
International names
and The domain and the network).
| Rule | What passes |
|---|---|
| Shape | At most 512 characters, with no spaces, control characters, backslashes or %00. The URL must already be canonical: parsing may not change its host or its path, so https://uloggd.com%2Eevil.com is refused. |
| Scheme and port | https:// only, on port 443. |
| Credentials | None: https://user:pass@... is refused. |
| Host | Lowercase letters, digits and hyphens, at least two labels, at most 253 characters, no trailing dot. No IP address in any form. |
| Reserved names | No localhost, and nothing ending in .localhost, .local, .internal, .lan, .home.arpa, .test, .example, .invalid or .onion. |
| International names | Written in ASCII (xn--), and accepted only while uloggd allows international names; otherwise url_denied with detail idn. See International names. |
| uloggd | Nothing on uloggd.com or its subdomains, blob.squarecloud.dev or *.squareweb.app, and no host that spells "uloggd" once look-alike characters and hyphens are folded away (u1oggd, ulo-ggd). |
| Path | Normalised, at most 256 characters, without .., //, %2e, %2f or %5c. |
| Query and fragment | None: uloggd adds its own query (see The frame). |
| Denylist | URL shorteners, ephemeral tunnels such as *.trycloudflare.com and *.ngrok-free.app, and hosts where unrelated games share an origin, such as html-classic.itch.zone, *.hwcdn.net, raw.githubusercontent.com, cdn.jsdelivr.net, unpkg.com, codepen.io, storage.googleapis.com and s3.amazonaws.com. |
The data events URL follows the same rules, except that its origin and path need not match the entry's.
These rules are for releases, which players open: port 443, no localhost,
and a denylist that includes ephemeral tunnels. The
development URL, which only you
open, has rules of its own: your own machine on any port, or a tunnel, with no
proof file and no check.
Never use a *.squareweb.app address for anything Arcade: not to host a game,
and not to fetch uloggd's keys. Those names belong to uloggd's hosting
provider, and a name released there can be claimed by another of its
customers.
International names
An address with characters outside ASCII, such as accented letters or
another alphabet, has an ASCII form that starts with xn--: jogo.ação.br
is jogo.xn--ao-siap.br. That is the form uloggd keeps and shows, to
players and in the portal, so that no address passes for another.
- Send the ASCII form. The API takes printable ASCII only, and refuses
anything else with
url_invalid. The portal converts what you type and shows the address it will save under the field. - Allowed only while uloggd accepts them. Otherwise saving, submitting
and making live a release that names one (the entry URL, the data events
URL or a third party) answer
url_deniedwithdetailidn, andfieldnaming the input. - Even then, each label must read as one honest name, or the answer is
url_deniedwith one of these indetail:
detail | The label |
|---|---|
idn_invalid | does not decode, is not the form a browser writes, or holds something other than letters, marks, digits and inner hyphens (emoji, symbols, invisible characters) |
idn_script | mixes scripts, or digits of different systems, beyond the usual mixes (Latin with Han and kana, Han with Bopomofo or Hangul), or uses a script uloggd does not accept |
idn_confusable | is written in another alphabet with letters that pass for Latin ones, or reads as uloggd |
The development URL never takes an international name.
The proof file
Serve this file at the root of the game's origin,
/.well-known/uloggd-arcade.json:
{ "apps": ["YOUR_APP_ID"] }- It must answer 200, parse as JSON, weigh at most 4 KiB, and list the app id the portal shows.
- Only whoever controls the root of an origin can publish it. That is the point: it proves the origin is yours, and it rules out hosts where many customers share one origin.
- The array exists for one case, handing the origin from one of your apps to another. Otherwise keep one id in it.
Let uloggd frame it
The entry URL must answer 200 with text/html, directly, and allow uloggd
as a parent:
Content-Security-Policy: frame-ancestors https://uloggd.com 'self'- Send no
X-Frame-Options: any value of it fails the check. - A redirect (any 3xx) fails the check. Register the final URL instead.
'self'keeps your own pages able to frame the game, for your own previews.- With no
frame-ancestorsat all the check passes too, as long as there is noX-Frame-Options, but then any site can frame the game. Sendingframe-ancestors https://uloggd.com 'self'still keeps other sites from framing it.
What the check does
uloggd's servers check the URL when you press Check URL, before submission, before approval, before a release goes live, once a day (at a time that varies by a few hours), and after a player reports the game.
- The requests carry the
uloggd-arcade-check/1User-Agent and no cookies, follow no redirects, give up after 5 seconds and read at most 512 KiB of a response. - The host must resolve to public addresses only.
- Three results block submission, approval and going live when they fail: the proof file, no redirect, and framing allowed.
- It records the network fingerprint: the addresses, the CNAME chain, the TLS certificate's issuer and key, and HSTS. It also records a hash of the entry document.
- It reads the entry document and up to 8 of its same-origin scripts, records a hash of each script, and extracts signals for the reviewer: the origins the page loads, password and payment fields, instructions to run commands, known trackers, and anything that imitates uloggd. Signals are shown to people; they never decide on their own.
An app gets 20 URL checks a day, the ones run on submission and on going live included.
On a live game, two failed checks at least an hour apart hide it until a check passes again. A new hosting provider together with a new TLS key hides it until a moderator reviews it again.
After approval, every check of a release is compared with the check that approved it. Reviewers see what changed between the two: the three results, the origins the page loads, forms and sensitive fields, the hash of the page and of each script, and the hosting provider and network.
In the portal, a check whose entry document differs from the one approved shows Changed since review. It is not a refusal and hides nothing: updates within what was reviewed may stay on the same URL, and the rest needs a new release (see What may change behind a URL).
The domain and the network
Every check records the registrable domain of the game's host: the name a
registrar sells, found in the Public Suffix List, a public list uloggd keeps
a copy of. orbit.example.com is under example.com; a host under a shared
suffix such as github.io is a domain of its own. Working it out asks
nothing of anyone. A check returned by the API carries it in
network.registrable_domain.
When uloggd turns on network lookups, a check also asks:
- the domain registry's RDAP service, for the registrar, the dates of registration, expiry and last change, the status and the name servers;
- RIPEstat, run by RIPE NCC, for the network (ASN), its holder and the prefix of up to four of the host's IP addresses.
uloggd never keeps the name or the contacts of whoever registered the domain. What it learns is shown to moderators and is in your account's data export. A lookup that fails or finds nothing never fails a check.
These can refuse a release when you submit it, when a moderator approves it and when you make it live:
url_deniedwithdetailnetworkandfieldentry_url: the host's network or its registrar is on uloggd's denylist. Some entries only flag the release for the reviewer.domain_too_new(409 conflict): for an account that was not invited, the domain was registered more recently than the minimum uloggd sets.
Both need the lookups: while they are off, nothing about the network or the domain's age refuses a release. To ask for such a refusal to be reviewed, write to the contact on the About us page.
What may change behind a URL
The same URL can keep serving a game that evolves. Without a new release you may ship fixes and content updates that stay within the reviewed description, rating, scopes and declared third parties. A rating change goes through the store page, not a new release.
These need a new release, and a new review:
- collecting new data;
- a new third party;
- any monetization;
- login flows;
- communication between players.
Each is judged against the terms' prohibitions, which forbid some of them outright: loot boxes, ads and trackers, and free chat between players (see Review policy).
Serving reviewers or uloggd's checks something different from what players get (cloaking), or switching the game's behaviour after approval, ends the account.
Immutable deployment URLs
Prefer URLs whose content never changes once deployed, one per build. A change then shows up as a new release, and rolling back means something.
- A path per build on the game's origin, such as
https://orbit.example.com/builds/1.4.2/, keeps one origin. Players keep the data the game saved in their browser, and the first-play notice is not shown again. The proof file stays at the origin's root. - A host per build, as Vercel, Netlify and Cloudflare Pages give for every deployment, works too, with three consequences. Each new host is a new origin, which your app claims on its first passing check, so the proof file must be in every build. The storage players had on the previous origin does not follow them, so keep saves on your server. And players see the first-play notice again, since it is shown for each new origin.
Making a release live compares the hosting provider with the one seen at approval: the last two labels of the final CNAME target, or the TLS issuer when there is no CNAME. Moving a game to another provider takes a new release.