Lechon Liga

Privacy notice

Version: privacy-notice-2026-09 · first published 2026-08-27 · revised 2026-09-07

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. Sa account mo, keyed hash lang ang numero mo ng telepono — may lihim na pepper; hawak ng Supabase Auth ang buong numero para maipadala ang one-time code, at naaalis iyon kapag binura ang account. May isang lugar kung saan nakasulat nang buo ang numero: ang payouts.recipient_mobile, ang talaan ng hiling mong maglabas ng pera, dahil may taong nagpapadala nito sa GCash at kailangan niyang malaman kung saan ipapadala. Naaalis iyon ng pagbura ng account sa bawat hiling na settled, failed o returned, at hindi tinatanggap ang pagbura habang may hiling pang nakabinbin (§4, §7.1). 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. Kaya mo ring alisin ang email mo sa waitlist nang mag-isa: may unsubscribe link ang bawat confirmation email, at ang pagsunod doon ay agad na nagbubura ng row mo. Nakakabit ang link na iyon sa referral code na hawak mo, hindi sa address mo — kaya walang matututunan ang kahit sino tungkol sa listahan sa pamamagitan nito. Ang row na hindi kailanman nakumpirma ay binubura pagkalipas ng 18 buwan; ang nakumpirma ay itinatago hanggang alisin mo. Kung wala ka nang link, maaari mo itong hilingin sa contact address sa legal block ng landing page. Hindi nakasulat dito ang address dahil setting iyon ng site, hindi linya ng dokumentong ito. Nakasulat ang lahat ng ito nang buo sa ibaba (§3.4, §10).

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-09).

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.

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.

Registration and payout update

The separately activated registration and payout flow uses the registration-2026-09 notice and payout-2026-09 conditions. They describe required birthdate, gender, occupation and city; optional adult location; and verified parent/guardian recipients for minors. The additional records are private registration profiles, recipient reviews, tax assessments and claim evidence. They do not give advertisers access to individual profiles. Registration and recipient-review data are removed on erasure; required financial evidence follows the finance retention rules below.

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

Tables written when a person uses the mobile app.
TableWhat it holdsWhyWho can read itRetention
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 A row only for the optional purposes you have acted on — personalised ads, marketing — with whether it is granted, the version of the document you were shown when you chose, and when you chose it. There is no row for operational processing and no code in this product writes one: running the game, paying prizes and catching fraud are necessary for the service and are not done on the basis of consent, which is why the operations switch on the privacy screen is shown locked on with the reason under it rather than as a choice. An absent row means you have not opted in, and both optional purposes are off until you do. It is the record of the optional choices you made. 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. Deleted after 35 days by a reaper the derby tick runs every five minutes, and which will not delete anything less than a day old — the day being what the cap is counted over. Everything older is audit trail.
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. Deleted after 30 days by a reaper that will not delete anything less than a day old. Erasure does not delete these rows — it removes the identifiers that point at a person, and the rows themselves age out.
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, timestamps, and recipient_mobile — the mobile number you verified at sign-up, in full, copied onto the request so the person who sends the money knows where to send it. It is the payout state machine, and the destination the operator pays. Own rows. Kept as financial history; the recipient mobile number on it is nulled by erasure once the request is settled, failed or returned.
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

Tables written when a company buys or funds something.
TableWhat it holdsWhyWho can read itRetention
advertisers Company name, contact person, status. It is who the campaign belongs to. Own row, and admins. Erasure replaces the company name and the contact person with the word erased and sets the status to disabled; the row itself stays, because the campaigns and the payments reference it. Section 7.4 lists the whole of what erasure does to an advertiser account.
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 while the account exists, so a served asset stays byte-identical: no client of ours can update or remove one. Erasing the account destroys it — section 7.1 — which is the one thing that does. Until 2026-09-07 nothing did, and this cell said so.

3.3 The talent

A talent has a users row — that is how a prize can be credited and paid — but never a biik. A talent also carries one row in consents that a player does not: the purpose talent_terms, written by talent_register at registration, recording which version of the Talent terms was accepted. It is the only consent row this product writes on your behalf; every other row in that table is one you made yourself on the privacy screen. 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. There is a confirmation email now, so that is buildable in a way it was not when this section was written; it is still written down as an open decision rather than half-built.

Retention: 18 months for a row that was never confirmed. A reaper runs on the same schedule as the rest of this system's housekeeping and deletes any waitlist row that is still unconfirmed 18 months after it was added — an address that never answered the confirmation email and never will. A confirmed row is kept until you remove it: confirming is how you tell us the address is yours and that you want to hear when the app opens, which is the only reason the list exists. This paragraph used to say "indefinite", and that was the true answer until the removal route below was built.

Removal route. Every confirmation email carries an unsubscribe link, and following it deletes your row immediately — no reply, no ticket, no waiting on us. The link is keyed on the referral code in it and never on your address, on purpose: a removal form that took an email address would answer "is this person on the list" to anyone who typed one, which is the same question this table's read side was changed in 2026-08 to stop answering. The link is also shown on the page itself, under the panel that appears when you join or confirm, since those are the moments you have your code in front of you. Account erasure still does not touch this table — a waitlist row is not attached to an account — and if you no longer have the link, you can ask us at the contact address published in the legal block of the landing page. That address is monitored and is shown there. This document does not print it: the landing page reads it from configuration, a static file such as this one cannot, and an address typed in here by hand would be the copy that goes stale.

3.5 Operator snapshots — a retention window over every table above

Before any change to the production database an operator takes a snapshot on their own machine; it excludes login session tokens, is readable only by that operator's account, and is deleted by the same script after at most 14 days. It is written down because it is a copy of the tables listed above that outlives an erasure by up to that window (section 7.1 says the same thing from the other end), and a notice that presented deletion as instant and total while these files existed would be describing only half of what happens.

4. Phone numbers: hashed on the account, and kept on a withdrawal while a person sends the money

A phone number reaches Supabase Auth, which needs it to send a one-time code. On your account row it is never held in full. What is stored there 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.

The one exception, stated plainly because it is one. The single table that holds a mobile number in full is payouts, in the column recipient_mobile. It is written when you request a withdrawal, copied from the number you verified at sign-up rather than from anything typed later, and it is there because the payout rail is a person: they read it from the withdrawal queue so they know where to send the money. No client of this product, in any role, is granted that column — only the server and the operator behind it.

What erasure does with it. Erasing your account nulls recipient_mobile on every withdrawal of yours that is finished — settled, failed or returned. Erasure is refused while a withdrawal is still in flight — requested, submitted, or of unresolved fate — and the refusal is an answer rather than a silent half-erasure: a request whose destination was wiped mid-transfer is money that can neither be sent nor returned. Wait for it to settle or fail, then erase; the erasure then clears the number with everything else section 7.1 lists.

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 hashed before 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.

One thing has to be true before it will run. Erasure is refused while a withdrawal is still in flight — while any request of yours is requested, submitted, or of unresolved fate. The control declines and says so; nothing is erased and nothing is half-erased. The reason is section 4's: erasing the destination of a transfer a person is in the middle of making is money that can neither be sent nor returned. Once that request settles or fails, ask again.

What erasure does, precisely:

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.

One limit of this section, stated rather than left out: erasure reaches the live database. The operator snapshot described in section 3.5 is a copy taken before a database change, and a row erased here can survive in one of those files for the rest of that snapshot's 14-day window before the script deletes it.

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.

7.4 An advertiser account

An advertiser signs in with an email address and a password rather than a phone number, and has a company row instead of a biik. The same endpoint erases it, and what it does is the same idea in that row's own terms:

One account can be both: somebody who signed up as a player or a talent and later registered a company holds a player row and an advertiser row. Erasure does both halves in one call, and doing only one of them — which is what an earlier version of this product would have done — is repaired the next time erasure is asked for.

8. What we do not do

9. Who else sees anything

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. Removal from the waitlist is self-serve too, through the unsubscribe link in the confirmation email, as section 3.4 describes. Everything else has to be asked for at the contact address published in the legal block of the landing page. That block is the route because it is the only place the address is held: it is configuration on the deployed site, not a line in this file, so a change of address changes one setting rather than three documents.

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-09 under its own version string.

privacy-notice-2026-09, first published 2026-09-05, supersedes privacy-notice-2026-08 of 2026-08-27. The entries below are all of one kind: the notice had drifted from what it describes. They are not counted in this sentence, because the count is the half that goes wrong the next time one is added — the version line at the top carries the date of the newest of them. The superseded wording is described below rather than quoted, deliberately: a guard in this repository fails the published notice on those exact sentences, and a changelog is a poor reason to reintroduce them. Unlike the Talent terms — where the superseded document stays published because consent records name it — this notice is revised in place, and privacy-notice-2026-08 is the version string for what it said before this date.

Corrected again on 2026-09-07, under the same version string. The entry for sections 3.2 and 3.3 below is that correction. The string did not move because what changed is the description of consent records this product does not keep: no processing, no retention period and no recipient changed, and nothing new is collected. A change to what is collected, why it is collected, how long it is held or who receives it gets a new version string — that is what the rule above is for, and it is the string a consent record names.