Lechon Liga
Privacy notice
Version: privacy-notice-2026-08 · first published 2026-08-27
Superseded on counsel review.
This notice is a factual description of what the Lechon Liga database and services actually store, written by the engineering team from the schema and the code. It is not legal advice and it has not been reviewed by counsel. When counsel reviews it, the reviewed document replaces this one under a new version string, and that version string is how you tell them apart.
Buod (Tagalog)
Ito ang listahan ng lahat ng iniimbak ng Lechon Liga tungkol sa isang tao: kung ano, bakit, gaano katagal, at sino ang nakakabasa. Hindi naka-imbak nang buo ang numero mo ng telepono sa mga table ng produktong ito — keyed hash lang, may lihim na pepper; hawak ng Supabase Auth ang buong numero para maipadala ang one-time code, at naaalis iyon kapag binura ang account. Hashed din ang IP address; hindi kami nag-iimbak ng buong IP. Walang analytics, walang tracking script, at walang third-party na script sa alinman sa mga page namin. Kayang burahin ang account mula mismo sa app (Privacy at pahintulot → Burahin ang account ko): naaalis ang personal na datos, pero nananatili ang ledger sa ilalim ng isang tombstone id na hindi na tumuturo sa iyo. Ang email sa waitlist ay wala pang self-serve na pagbura — kailangan itong hilingin sa contact address sa landing page, at wala pa kaming naitatalagang address hanggang ngayon; nakasulat iyon nang buo sa ibaba.
English is the reference text of this notice. A full Tagalog rendition follows counsel review.
1. What this document is, and what it is not
This notice describes behaviour that already exists in code: every table listed below is a table in the shipped database schema, and every retention and access statement is derived from that schema, its row-level security policies and its column grants.
Player Terms, advertiser terms and contest rules are not written yet. If you came here looking for them, they do not exist on this site and nothing on this site pretends they do. They are with counsel, because they are instruments that bind rather than documents that describe, and drafting them is not an engineering task. The two documents that do exist are this notice and the Lechon Liga Talent terms (talent-terms-2026-08).
2. Who this is about — the three personas
Three kinds of person use Lechon Liga, they sign in through three different doors, and the database holds different things about each of them.
- Player. The mobile app. Signs in with a phone number and a one-time code, or with Facebook. Owns a biik (a piglet), feeds it by watching sponsor videos, and can be credited a prize after a Biik Derby.
- Advertiser. The web portal. Signs in with an email address and a password. Schedules and pays for ad campaigns and posts briefs to the Talent Arena.
- Talent. The same web portal through its own door — a phone number and a one-time code, not the advertiser's email and password. Enters advertiser-funded briefs and can be awarded a prize.
A fourth role exists internally: an admin is an operator of this company, listed in a table the product never publishes, who reviews campaigns and briefs. An admin is one of us, not a persona this notice is written for.
3. What each table stores, why, and who can read it
The default is deny. Every table has row-level security switched on, clients hold explicit per-column SELECT grants rather than table-wide ones, and no client of any kind can write to a money table — every balance-moving write goes through a server-side function that only the API service can call. "Own row" below means row-level security limits the read to rows whose user id is the caller's own.
3.1 The player
| Table | What it holds | Why | Who can read it | Retention |
|---|---|---|---|---|
auth.users |
The sign-in identity: a phone number in E.164 form, or an email address, held by Supabase Auth. | It is what a sign-in matches against. | The account itself, and this company's servers. | Until erasure, which nulls the phone number and the email address. |
users |
A keyed hash of the phone number (never the number), a Facebook user id if the account signed in that way, a self-reported city and barangay code, a verification level, an opaque payout-identity reference, a quarantine timestamp, a deletion timestamp, an invite code and the account that referred it. | One account per phone; payouts; coarse geography for local derbies; fraud scoring. | Own row. | Erasure nulls the phone hash, the Facebook id, the payout-identity reference, the
city and the barangay, and stamps a deletion time. Four things deliberately survive it: the verification
level, the quarantine timestamp, the invite code and the account that referred
this one. The anti-abuse reason is in the erased_identity_bindings
row further down this table, and section 7.1 lists the whole of what erasure
does. |
consents |
One row per purpose — operational, personalised ads, marketing — with whether it is granted, the version consented to and when. | It is the record of what you chose. | Own row. | Held as current state; a change overwrites the row. |
consent_events |
Every grant and every revocation, with the version string and a timestamp. | The audit trail behind the row above. | Nobody through the client; the API and operators only. | Append-only and permanent. The table rejects UPDATE and DELETE, so a consent record can never be corrected — only explained. |
pigs |
The biik: its name, weight, fitness, care streak and cosmetics. | It is the game state. | Own row. Only the name and the cosmetics are writable by the app. | Until erasure, which renames it. |
care_events |
Each pet and each wash, with a timestamp. | It is what the daily care cap is counted from. | Own row. | Kept; no reaper exists. |
ad_views |
One row per completed sponsor video: which network and campaign, the network's own transaction id, a fraud score, a timestamp, a hashed IP address and a device fingerprint. | Feed credit is paid once per view, and repeat-abuse scoring needs the view history. | Not readable by any client. | Kept; no reaper exists. Erasure does not delete these rows — it removes the identifiers that point at a person. |
feed_nonces |
A single-use token id bound to an account, with its issue and expiry times and whether it was redeemed. | It binds one reward to one view and stops a replay. | Not readable by any client. | Deleted after expiry by a reaper that will not delete anything less than a day old and defaults to seven days. |
race_entries |
Which biik ran in which race, the frozen race-day stats the simulation consumed, the finish rank and the prize. | It is the race result. | Rank and prize are public. The frozen stats are deliberately not published to any client — every entrant would otherwise be able to read every rival's. | Kept — it is the race record. |
ledger |
Every movement of in-app balance: the amount, the reason, a reference and the balance after. | It is the money record, and it is what the public proof-of-reserves page is computed from. | Own rows. | Append-only and permanent. Erasure deliberately leaves it intact under a tombstone id, so the accounts still add up while the person is gone. |
payouts |
A withdrawal request: the amount, the rail, its status, the rail's own reference and timestamps. | It is the payout state machine. | Own rows. | Kept as financial history. |
erased_identity_bindings |
Written only at erasure, and only when there was something to write: the tombstone id, the hashed phone, a hash of the payout-identity reference, and whether the account was under quarantine. | So that erasing an account is not a way to clear an anti-abuse decision and start again. | Nobody through the client. | Append-only. It carries no name, no city, no device and no raw number — only hashes, which are useless for identifying or contacting anyone. |
deletion_receipts |
A hash of the confirmation code issued for a deletion request, which channel it came through, and what it found. | It is the evidence that a deletion request was carried out. | Read through the public status endpoint, which answers only "done" or "unknown" for a code the asker already holds. | Kept indefinitely, on purpose. It carries no personal data and no link back to the account; a row that aged out would be an erasure we could no longer prove we performed. |
3.2 The advertiser
| Table | What it holds | Why | Who can read it | Retention |
|---|---|---|---|---|
advertisers |
Company name, contact person, status. | It is who the campaign belongs to. | Own row, and admins. | Kept while the account exists. |
campaigns, briefs |
What was scheduled or asked for: targeting, budget, dates, the creative reference, the brief text and its prize. | It is the product the advertiser bought. | The owning advertiser and admins. A brief becomes visible to talents only once it is paid for. | Kept as commercial history. |
campaign_payments, brief_payments,
payment_events |
The checkout session with the payment provider, its identifiers, the amount, and the raw evidence of every provider webhook. | Money must be booked exactly once, and a disputed payment has to be resolvable from durable evidence. | No client at all. None of the three carries a column grant, and
a database checks that before it checks any row rule, so a query sent from a
browser is refused outright. brief_payments and
payment_events have no read policy either. campaign_payments
does — an own-or-admin rule named campaign_payments_read_own_or_admin,
created with the table and never dropped — and the withheld grant is the only thing
standing between it and a reader; we name it here rather than leave it out, because
somebody auditing this notice against the schema will find it. Payment state
reaches the browser only as a campaign or brief status. |
Kept as financial history. |
Storage bucket creatives |
The uploaded ad video or image. | It is what players are shown. | The uploading advertiser and admins; served to players through the feed. | Kept; there is no delete path, deliberately, so a judged or served asset stays byte-identical. |
3.3 The talent
A talent has a users row and a consents record like a player does —
that is how a prize can be credited and paid — but never a biik. What is additional is in the
Talent terms, section 10, which describes
talents, brief_entries and the entry-media bucket in the same
table-by-table form as above.
3.4 The waitlist
The waitlist table holds an email address, a referral code
minted for it, the code that referred it if any, a confirmation timestamp and the time it was
added. It exists so that we can tell people when the app opens. It is invisible to every
client: the table has row-level security on with no read policies at all, and the only door
into it is one function that takes an address and answers a referral link on a first join or
a bare "already joined" on a repeat.
What that does and does not hide. The two answers differ, so submitting an address does still reveal whether an address is already on the list — and we would rather say so here than let this read as a guarantee it is not. What was closed, in 2026-08, is everything else that used to come back on a repeat: the position in the queue, the count of confirmed referrals, and the referral code, which is the credential inside a referral link and could be used to attribute someone else's sign-ups. Removing the last distinction would mean returning no referral code on a genuine join either and mailing the link instead, and this system has no email sender. It is written down as an open decision rather than half-built.
Retention: indefinite. There is no expiry on a waitlist row and no reaper that removes one.
Removal route, stated plainly because it is a gap. There is no self-serve way to leave the waitlist. Account erasure does not touch this table — a waitlist row is not attached to an account. The route is therefore to ask us, at the contact address published in the legal block of the landing page, and no monitored address has been named yet: until one is, the landing page shows no contact line, because an address that bounces is a worse answer than none. Naming one address is the first item on this company's own handoff list, and a self-serve removal is planned for the next database change. Both facts are written here rather than dressed up, because a notice that described a route that does not exist would be the exact failure this document was written to correct.
4. Phone numbers never reach this product's own tables
A phone number reaches Supabase Auth, which needs it to send a one-time code. It never
reaches this product's own tables. What is stored is
HMAC-SHA256(number, pepper) — a keyed hash, where the pepper is a secret held
outside the database, provisioned by an operator, and never committed to the source
repository.
The key matters. A plain SHA-256 of a phone number is reversible in minutes: there are only so many Philippine mobile numbers, and anyone can hash all of them. A keyed hash is not, unless the key leaks with it. The function that computes it fails closed: if the pepper is missing it raises an error rather than quietly hashing with an empty key and calling the result a secret. The pepper can be rotated, and the schema records the fingerprint of the pepper in force so a rotation cannot silently orphan every stored hash.
5. IP addresses are hashed, and the cluster signal is switched off
When a sponsor video completes, the API takes the caller's IP address as its own proxy saw it, hashes it with a keyed hash, and stores only the hash. A raw IP address is never written to the database.
The fraud scorer's IP-cluster term — "how many accounts share this address today" — is weighted at zero, deliberately, because carrier-grade NAT is normal in the Philippines and that term punishes households and neighbourhoods for sharing an address. The hash is still stamped on the row, so the signal remains available if a better rule is ever written for it.
6. Device fingerprint
A feed completion has room for a device identifier, and no app we have shipped sends one:
the field exists for a future build, and today every view row's copy is empty. If a build
does send one, it is stored on the view row — the only place it is kept — and used for one
thing: noticing that many accounts are being farmed from one device. The account row holds
no device identifier, so erasure nulls none; what erasure removes is the identity on the
account, and the view rows survive it, as the ad_views row in section 3.1 says.
7. Deletion
7.1 From inside the app
The mobile app carries the control: Privacy & consent → Delete my account, which arms on the first tap and runs on the second. It calls an authenticated endpoint that can only ever erase the account making the call.
What erasure does, precisely:
- nulls, on the account row, the hashed phone, the Facebook id, the payout-identity reference, the city and the barangay, and stamps a deletion time;
- renames the biik;
- for a talent, replaces the handle with a value that names nobody, and clears the display name, bio, portfolio link and skills — while the entries themselves survive, because they are work an advertiser paid for and a ranking board inside the arena records that a brief was won. That board (Bantog) is behind a login: active talents, active advertisers and admins, and nobody else — the Talent terms, section 10, say the same;
- nulls the phone number and email address held by Supabase Auth;
- writes the anti-abuse binding described in section 3.1, but only when there was a hashed phone or a payout reference to record;
- and does not touch the ledger, the pool ledger, the pool outflows or the consent events. Those four are append-only financial and audit history and they survive under the tombstone id — an id that, after the nulling above, points at nobody.
It is idempotent: asking twice reports that it was already done rather than pretending to do it again. The account row itself is not hard-deleted, because the ledger references it and the bare id is not personal data once every field beside it is gone.
7.2 Through Facebook
An account that signed in with Facebook can ask Facebook to delete its data. Facebook calls a callback on our API with a signed request; we verify that signature, find the account by its Facebook id, run exactly the same erasure, and return a confirmation code and a status URL — which is what Facebook's data-deletion contract requires. The status URL is public and unauthenticated by design, and it answers only done or unknown for a code the asker already holds.
7.3 The deletion receipt
Every completed request appends a row holding a hash of that confirmation code, the channel it arrived through, and the outcome. It holds no personal data and nothing that links back to the erased account. It is kept indefinitely, because it is the only evidence that the erasure happened.
8. What we do not do
- No analytics, and no tracking of any kind. No page on this origin, and no screen in the app, loads an analytics script, a tag manager, a session recorder or any third-party script. There is nothing to opt out of because there is nothing there.
-
No advertising cookies, and no advertising storage. Nothing kept on your
device is used for advertising, for tracking you between sites, or for building a profile.
This is the complete list of what is kept there, which is short enough to print:
-
lang— on the four web pages that carry a language switch (the landing page, the advertiser portal, Ang Kaban and the install page) and in the app, remembering whether you chose English or Taglish. It is the choice itself and nothing else. This notice is not one of them: it carries no script at all, and the Tagalog summary at the top is printed beside the English rather than switched to. - The sign-in session, on the pages that have a sign-in and in the app. It is what keeps you signed in, and signing out removes it.
-
portal:draft:and your account id, on the advertiser portal only. It holds the id of the campaign draft you were last editing, so that a reload does not start a second one. It is an id, not content, and it is checked against the database before it is used at all. -
portal:pending-registration:and your email address, on the advertiser portal only. Between typing the sign-up form and confirming your email, this parks the company name and contact person you typed, so that they are not lost while you go and read a confirmation mail. It is dropped the moment the registration completes, and when you sign out. Otherwise it lives at most seven days: after that the sign-up screen treats it as absent and deletes it unread the next time it looks. It is keyed to the email address so that a shared machine — an internet café, a family laptop, a shop counter — cannot hand one advertiser's details to the next person who signs in on it. -
biik.lastWeighIn— in the app only. One number: your biik's weight the last time you opened its screen, so the meter can draw a mark for where it stood.
-
- No selling, and no sharing for advertising. Personal data is not sold and is not handed to an ad network. Sponsor videos are served by this product itself, from our own storage, not by an ad network's SDK.
- No purchase, ever. A player cannot pay money into this product. There is no in-app purchase and no deposit, so there is no card of yours for us to hold.
9. Who else sees anything
- Supabase hosts the database, authentication and file storage, and therefore processes everything above.
- The payment provider processes an advertiser's card payment. We hold the session and event identifiers it gives us; we never see or store a card number.
- The payout rail would receive a winner's payout details. It is switched off in production today: the shipped configuration refuses every payout request, so no payout data has left this system.
- Facebook, for accounts that sign in with it, and for the deletion callback described above.
10. Your rights, and what to do
Under the Philippine Data Privacy Act you can ask what is held about you, ask for it to be corrected, ask for it to be erased, and object to processing. Erasure of an account is self-serve today, in the app, as described in section 7. Everything else — including removal from the waitlist — has to be asked for at the contact address published in the legal block of the landing page, and as section 3.4 says without softening it, that address does not exist yet.
One consequence of the append-only design is worth stating: a consent record and a money row cannot be edited or deleted, by anyone, including us. They can only be added to and explained. That is what makes the public proof-of-reserves page meaningful, and it is a deliberate limit on correction.
11. Changes
This notice is versioned, not dated-and-quietly-edited. The version string above is the
identifier; a materially different notice gets a new one. The counsel-reviewed replacement
will supersede privacy-notice-2026-08 under its own version string.