Product engineering · September 2026 · 8 min
Where does trust live in a product?
A conversation about privacy, money and the parts of a product that users have to believe will behave properly.
When you say trust is part of the architecture, what do you mean?
I mean I cannot solve trust by writing reassuring copy after the product is built. Tilawa deals with people’s identities, live recitation, teacher access and payments. If I say a conversation is private or a person will only be matched in a certain way, I need to know what in the system makes that true.
The interface matters because it explains what is happening, but the interface is also the easiest layer to bypass. There can be a bug, another client or a direct request to the backend. So while I was building, I kept asking what would still protect the user if the screen did the wrong thing.
How did that affect the way you handled the audio?
I decided very early that I did not want to record lessons. A recording sounds useful because the learner could play a correction again, and I understand that argument. The moment I store the audio, though, I also create questions about who can hear it, how long it stays, how it is deleted and what happens if the storage is exposed later.
Tilawa’s calls are peer to peer where possible and relayed when the network needs it, but there is no recording feature and no path that stores the lesson audio. I would rather avoid collecting something sensitive than collect it and rely on a promise that it will always be handled perfectly. It means giving up some possible features, but I was comfortable with that for this product.
The app also has gender-matching rules. Why was the database involved in that?
Because filtering the list in Flutter only changes what somebody sees on that screen. It does not stop a request from being made another way. The product is meant to match sisters with sisters and brothers with brothers, and that cannot be a rule that works only while the current interface behaves.
I enforced the boundary with row-level security in Postgres as well. A signed-in account should still be unable to read data outside the rule. The app can explain the experience in a respectful way, while the database handles the part that should not depend on presentation.
Was signing in enough to establish who could do what?
No, because being authenticated only tells the system which account is making the request. It does not mean that account should be allowed to do everything. A teacher has an approval state before becoming visible. A student, teacher and administrator do not need the same access. Even within administration, somebody handling a particular function should not automatically have every permission.
This became more obvious as the admin side grew. It is very easy to use a powerful service credential to make development convenient, then discover that the application has stopped making careful permission decisions. I wanted the permission to belong to the role and the action, not to whichever screen happened to expose the button.
Payments are usually where people become very sensitive about trust. What did you have to account for?
The difficult part was accepting that a payment does not happen as one neat request and response. The network can drop after the user has approved the charge. A webhook can arrive more than once. The user can close the page. The provider can know the payment succeeded while our page is still waiting.
So the same payment request needs to be safe to retry without crediting twice. The webhook has to be verified. There has to be a reconciliation path for payments that are left in an uncertain state. On the customer side, the experience should still be simple: they pay once, the right account receives the right minutes and there is a clear support route if that does not happen.
Most users will never know that idempotency or reconciliation is there, which is fine. They should not need to know our internal vocabulary before they can trust us with their money.
Can a trustworthy system still fail?
Of course. Phones lose their connection, services become unavailable and code still has bugs. For me the real question is what the failure is allowed to do. If a call never becomes a proper session, it should not consume paid minutes. If a payment response gets lost, we should be able to find out whether money moved instead of asking the customer to guess. If a feature starts behaving badly, I should be able to stop new activity without cutting off a call that is already in progress.
I became less interested in saying that something could not fail and more interested in the state it would leave behind when it did.
You also found an issue with the privacy wording before release, didn’t you?
Yes. The website mentioned email collection while the main authentication flow was using phone numbers, and there was also a privacy manifest that existed locally but was not included in the Xcode target. These are the kinds of things that can happen as a product changes and the surrounding documents do not move at the same speed.
It made me take the wording more seriously. A privacy page is not correct because it sounds responsible. It has to describe the app that is actually in somebody’s hand, including the services and permissions inside that particular build. I now see those documents as another part of the release, not something to write once and forget.
What do you check now when a feature makes a promise to the user?
I look for where that promise is kept after the screen is gone. For session time, the server is responsible. For the use of paid minutes, the backend makes the decision. For access to private records, the database has its own rules. If the only protection is that a button is hidden, I know I have not really enforced anything yet.
I also ask whether we can explain what happened afterwards. Trust is affected by the original failure, but it is also affected by whether support can give the person a truthful answer. Sometimes the most reassuring thing a product can do is leave a clear record and recover without making the user fight for it.