Key takeaways

  • The link hash salt, the postback secret key and the API key belong on your server only; the App ID and the single-use SDK token are the only values your app or browser should ever see.
  • Load keys from environment variables or a secrets manager, keep .env files out of Git, and remember that GitHub does not recognize Sharklio keys unless you add a custom pattern.
  • When a key leaks, replace it in the Integration tab first, deploy the new one, then check your own logs and compare your credits with Sharklio's conversion records.

Where should you store API keys securely?

Store API keys and other secrets on your server, never in anything you ship to users. Read them at runtime from environment variables set on the server or, better, from a secrets manager, and keep them out of your code repository, your logs and your chats. For a Sharklio publisher, that means the link hash salt, the postback secret key and the API key stay on your backend, while your website or app only ever receives a finished signed link or a short-lived session token. If a key ever leaks, replace it in your dashboard first and investigate second.

For the click-by-click rotation steps, see How to rotate your keys and link salt.

The Sharklio credentials you have

Every app has its own set of values in your dashboard under Apps, your app, Integration, in the section 2. Keys and security. The section header says it plainly: “These keys belong on your server only. Never put them in a browser or an app.” Keys are masked until you click Show.

ValueSecret?What it doesIf someone else gets it
App IDNoIdentifies your app in the offerwall link, and goes into the mobile SDKs when you initialize them.Nothing on its own. It is in every link your users open.
Link hash saltYesSigns the hash in every offerwall link with HMAC-SHA256.They can build valid links for any user ID and open that user’s offerwall and My offers.
Postback secret keyYes, the most sensitiveSigns every postback, so your server can tell real credits and reversals from fake ones.They can forge postbacks your server accepts and credit any account with currency you were never paid for.
API key (starts with shk_)YesAuthenticates your server to the Offers API, the Reporting API and POST /v1/session for the mobile SDKs.They can load offers and create offerwall sessions as your app for any user ID, read your reports, and use up your app’s request limit.
SDK session token (starts with sst_)Short-livedOpens the offerwall once in your app. It works once and for 10 minutes.Very little. It is meant to reach the device.
A user’s signed offerwall linkPersonalOpens the offerwall for that one user.It opens that user’s offerwall, so do not publish one user’s link for everyone.

There is one API key per app, not one per API: the Reporting API docs say to use the same key as for the Offers API, and the SDKs use it for sessions too, so replacing it affects all three.

Why keys never go in apps, browser code, repos or chats

A secret stays secret only while nobody outside your server can read it. Each of these places breaks that rule.

App binaries

Anything compiled into an Android, iOS, Unity, Flutter or React Native build can be pulled out of the downloaded file, whether it sits in code, a resource or a build flag. The Sharklio Android SDK docs put it bluntly: never put the API key or the link hash salt in your app, “because anyone can read an app’s files.”

Frontend JavaScript

Any JavaScript you send to a browser can be read in the developer tools, and build tools make this easy to get wrong. The Vite docs say variables prefixed with VITE_ are exposed in client-side code and should not contain sensitive information such as API keys, and the Next.js docs say a NEXT_PUBLIC_ variable is inlined into the JavaScript sent to the browser. So never name a Sharklio key VITE_SHARKLIO_API_KEY or NEXT_PUBLIC_SHARKLIO_LINK_SALT. Unprefixed variables stay on the server in both tools.

Public GitHub repositories

Once a key is in a commit, deleting the file in a later commit does not remove it. It stays in the history, in every clone and in any fork. Treat a key that was ever pushed to a public repository as leaked, even if the push lasted a minute.

Discord, chats and support threads

A key pasted into a Discord channel, a forum post, a screenshot or a chat with an AI assistant has left your control: others can copy it before you delete the message, and many servers log messages with bots. Nobody needs your key to help you; share your App ID instead. The Sharklio AI agent setup docs follow the same rule: the ready-made prompt for coding agents contains no keys and tells the agent never to ask for one.

Common ways publishers leak keys

Building the offerwall link in the browser with the salt in a script, shipping the API key in a mobile app to call /v1/session directly, committing a .env file, and pasting a postback handler with the key filled in when asking for help.

How the Sharklio integrations keep keys off the device

Every Sharklio integration is designed so that your keys stay on your server:

  • Offerwall link, iframe and floating button: your server computes the hash for the signed-in user and sends only the finished link to the page. The embed.js floating button only needs that link, never the salt.
  • Mobile SDKs: when the user taps Earn, your app asks your own server for a token. Your server calls POST /v1/session with the API key, then returns only the token to the app. The app gets the App ID and a single-use token, nothing else.
  • Offers API and postbacks: your server requests the offers, and your server checks each postback signature (see What is HMAC?). The browser never takes part.

Here is the link step in PHP, reading the salt from the environment and stopping if it is missing:

$linkHashSalt = getenv('SHARKLIO_LINK_SALT');
if ($linkHashSalt === false || $linkHashSalt === '') {
    throw new RuntimeException('SHARKLIO_LINK_SALT is not set');
}
$userId = (string)$currentUser->id;
$hash   = hash_hmac('sha256', $userId, $linkHashSalt);
$link   = 'https://wall.sharklio.com/' . $appId . '?user_id=' . rawurlencode($userId) . '&hash=' . $hash;

The same rule applies to the SDK token endpoint: take the user ID from your own sign-in, never from the request, and return only the token. The Android offerwall guide shows the full flow.

Store keys in environment variables or a secrets manager

Pick the strongest option your hosting supports.

Where the key livesGood forWatch out for
Hardcoded in source codeNothingIt ends up in Git and every copy of the project.
Environment variables (host settings or a .env file outside Git)Most websites, bots and small backendsVisible to every process on the machine and can end up in crash dumps or debug pages.
A secrets manager (AWS Secrets Manager, Google Cloud Secret Manager and similar)Teams, several servers, audit requirementsNeeds access rules, and costs a little to run.

Environment variables

Use the same names as the Sharklio docs and the dashboard’s coding agent prompt: SHARKLIO_LINK_SALT, SHARKLIO_POSTBACK_KEY and SHARKLIO_API_KEY. Most hosts have a secret or environment settings page for them. For local development, keep them in a .env file and make sure Git ignores it:

# .env (never committed)
SHARKLIO_LINK_SALT=paste-from-the-integration-tab
SHARKLIO_POSTBACK_KEY=paste-from-the-integration-tab
SHARKLIO_API_KEY=paste-from-the-integration-tab
# .gitignore
.env

Node.js reads the file with node --env-file=.env server.js, a flag the Node.js CLI docs list as stable since versions 22.21.0 and 24.10.0. In Python, os.environ["SHARKLIO_API_KEY"] raises an error when the variable is missing, which beats signing links with an empty salt.

On PHP-FPM, variables from your shell do not reach PHP by default: the PHP manual lists clear_env with a default of yes. Pass them in the pool configuration instead, and keep that file readable only by root:

env[SHARKLIO_LINK_SALT] = paste-from-the-integration-tab
env[SHARKLIO_POSTBACK_KEY] = paste-from-the-integration-tab
env[SHARKLIO_API_KEY] = paste-from-the-integration-tab

A secrets manager

The OWASP Secrets Management Cheat Sheet warns that environment variables are generally accessible to all processes and may be included in logs or system dumps, and prefers other ways of injecting secrets where possible. With a secrets manager, your code fetches the key at runtime and access is limited to the services that need it; the AWS Secrets Manager guide describes replacing hard-coded credentials with a runtime call, with logs of the API calls when AWS CloudTrail is on. For one small server, a locked-down environment variable is a reasonable start; move to a manager once more than one person or server touches the keys. Either way, turn on Login verification for your Sharklio account too (Secure your Sharklio account), since anyone in your dashboard can read the keys.

Never log a key

OWASP says secrets should never be logged. Postbacks carry a hash, not the key, so logging them is fine; the Authorization header of your own API calls is not.

GitHub secret scanning and push protection

GitHub has two features that catch leaked credentials. Its secret scanning docs say scanning runs automatically and for free on public repositories and covers the entire Git history on all branches. Its push protection docs say push protection for users is enabled by default and stops you from pushing secrets to public repositories. Private organization repositories need GitHub Secret Protection for both. (Checked on October 29, 2026.)

The catch: both features look for known patterns. Sharklio is not on GitHub’s list of supported secret patterns, and the salt and postback secret key are plain hex strings with no prefix, so GitHub will not flag them on its own. Two things help:

  • Custom patterns. In organization repositories with Secret Protection, you can define your own regular expressions and turn on push protection for them. The API key always starts with shk_, so shk_[0-9a-f]{40} matches it.
  • Your own check. Before you make a repository public, search its whole history for the API key format and for the variable names with a value after them:
git grep -nE 'shk_[0-9a-f]{40}' $(git rev-list --all)
git grep -nE 'SHARKLIO_[A-Z_]+=[^[:space:]]' $(git rev-list --all)

A hit in an old commit means the key is in the history even if today’s files are clean, so replace that key first.

What to do when a key leaks: rotate first, then check the logs

Speed matters more than the investigation. OWASP says exposed keys “should undergo immediate revocation”, and GitHub’s guide on removing sensitive data from a repository says the first step is to revoke or rotate the secret, because rewriting history cannot reach other people’s clones or forks.

  1. If your Sharklio login may be the source, secure the account first. Anyone signed in to your dashboard can click Show and read the new key. Follow What to do if your account is compromised, then come back.
  2. Have the deploy ready. The old key stops working the moment you replace it, and there is no overlap period.
  3. Replace the key. In Integration, under Replace your keys, click New link hash salt, New postback secret key or New API key and confirm. The box says: “Use this if a key may have leaked. The old one stops working right away.”
  4. Deploy and test. Put the new key on your server, restart what reads it, then use Check a link and Send a test postback in Integration status (How to test your app).
  5. Check the logs, using the table below.
  6. Clean up the source of the leak: delete the message, remove the file, rebuild the app, and tell your team.
Leaked keyBreaks until you deploy the new oneWhat to check afterwards
Postback secret keyYour hash check rejects real postbacks.Every credit your server recorded during the exposure has a transaction_id. Compare them with Reports, Conversions or the Reporting API’s GET /v1/conversions. A credit that Sharklio has no record of was forged. Resend any real postbacks your server rejected while you switched keys.
API keyOffers API, Reporting API and SDK session calls fail with invalid_api_key.Review your own server logs for unexpected callers, and look at the Offers API and Mobile SDK rows in Integration status for activity you cannot explain.
Link hash saltLinks built with the old salt show Link not valid, including links in emails you already sent.Review user complaints about offers or history they do not recognize.

Reports, Postbacks only lists what Sharklio sent, so forged calls never appear there, only in your own logs; that is why postback security recommends logging every call with its transaction ID and signature result. A postback you resend after rotating is signed with the new key, so resending failed postbacks is safe. If you find forged credits, take them back and hold cashouts for those accounts. The Reporting API quick start shows how to page through conversions, and an IP allowlist for postbacks can add a layer, never a replacement.

Built so your keys never leave your server

Sharklio is a pay-per-result affiliate network, and every way to run it, from the signed offerwall link to the mobile SDKs with single-use session tokens, keeps your keys on your backend, with a signed server-to-server postback for every credit and reversal. See how the wall works, or get in touch if you have a question about your setup.

Frequently asked questions

Is it safe to put an API key in frontend JavaScript?

No. Anyone can read the JavaScript a browser downloads. Call the API from your server and send the browser only the result, such as a signed offerwall link or a list of offers.

Is the Sharklio App ID a secret?

No. The App ID is part of every offerwall link and goes into the mobile SDKs. The link hash salt, the postback secret key and the API key are the secrets.

Is the Reporting API key different from the Offers API key?

No. Each app has one API key, used for the Offers API, the Reporting API and mobile SDK sessions. Replacing it affects all three.

Does deleting a leaked key from GitHub fix the leak?

No. The key stays in the Git history, in clones and in forks. Replace the key in your dashboard first; once the old key is dead, cleaning the history is optional.

What happens to postbacks after I replace the postback secret key?

From that moment, postbacks are signed with the new key, so your server rejects them until you deploy it. Failed postbacks are retried and can be resent from Reports, Postbacks, signed with the new key.