There are two patterns to start with.
It's important to think about who/what will send the transaction to the contract.
Server-Centric option: If it's the server, then the contract would check that the sender is the trusted server address and then proceed on the basis that the server is trustworthy and wouldn't tell it to send funds to a non-user. This means the overall system is quite centralized and depends on the server for the logic to work.
Consider:
modifier onlyServer {
require(msg.sender == serverAddress);
_;
}
function receive(address payable receiver, uint amount) public onlyServer {
receiver.transfer(amount);
emit ...
}
Whitelist Option: If it's the user (their browser), then the contract would check an approved whitelist of addresses that are allowed to use the function. The server would still have a "role" in the contract. It would add and remove users from the whitelist. That would be less centralized but not entirely decentralized in that the central authority is still important.
Consider:
mapping(address => bool) public userAuthorized;
modifier onlyUser {
require(isUser(msg.sender));
_;
}
function isUser(address candidate) public view returns(bool isIndeed) {
return userAuthorized[candidate];
}
function receive() public view onlyUser {
msg.sender.transfer(amount);
emit ...
}
function addRemoveUser(address user, bool isAuthorized) public onlyServer {
userAuthorized[user] = isAuthorized;
emit ...
}
It would be difficult to implement a combination even though that might seem like the intuitive way to approach this. If the user kicks off the transaction and then the contract is to check with the server, first, before sending funds, that is a multistep process that involves using an Oracle pattern and still puts the server in the center of a centralized process. So, more complex with questionable benefits.
Hope it helps.