Skip to content

Publishable and secret keys: keeping verification data off the client

Put the publishable key in your app and keep the secret key on your server. A publishable key can start a verification and read minimal status, never identity data, so plan for it to leak and limit what a leak costs.

Charles Archibong

, Co-founder

· 6 min read

Headline "Which key goes where" beside an illustration of a key beside a one-time code, on a deep indigo gradient.

Key takeaways

  • Publishable keys ship in clients and can start verifications and read minimal status, never identity data.
  • Secret keys read full results, captured media and every Risk Intelligence endpoint, so they stay on your server.
  • Assume any client key will be extracted, and restrict it to your web origins where it runs in a browser.
  • Revoking a key takes effect on the next request, so rotate by creating the replacement first.

Myaza Trust gives you two kinds of API key, and the rule for using them is short: the publishable key (pk_) goes in your web page or mobile app, and the secret key (sk_) never leaves your server. A publishable key can start a verification and read a minimal status. It cannot read a name, a date of birth, an ID number or a selfie. A secret key can read all of that.

To restrict a leaked key, you have three levers: an allowlist on the key (web origins for a publishable key, IP addresses or ranges for a secret key), per-service keys so one revocation does not take down everything, and immediate revocation. The rest of this article explains why the split works the way it does and how to use those levers before you need them.

Why does a verification API need two kinds of key?

Because any key you put in a client will eventually be read by someone you did not intend. A mobile app can be decompiled. A web bundle can be opened in developer tools. Treating a client key as secret is a hope, not a control.

So the design assumes the client key leaks and limits what it can do. The table below is taken from the authentication documentation.

Key

Prefix

Where it lives

What it can call

Publishable

pk_test_ / pk_live_

Your frontend or mobile app, inside the SDK

Start a verification (/verify), upload captures (/upload), read SDK configuration (/config), read minimal status (/status: state and reason, no personal data)

Secret

sk_test_ / sk_live_

Your backend only

Full verification results and media, plus every Risk Intelligence endpoint under /api/v1

A key is its prefix followed by 32 random alphanumeric characters, for example pk_test_aB3dE5fG7hJ9kL1mN3pQ5rS7tU9vW1xY. The prefix tells a human reviewer at a glance which scope and environment they are looking at, which is useful when a key turns up in a log or a pull request.

What can someone do with a leaked publishable key?

They can start verifications against your account and poll their minimal status. They cannot read the result of any verification, yours or theirs, because the full result endpoint refuses a publishable key with 403 secret_key_required.

That is a meaningful limit, but it is not zero. In Production, each completed verification is billed to your credit balance, as the environments documentation sets out. A publishable key copied onto another website could be used to run checks you pay for. That is the risk the origin allowlist below exists to reduce.

What can someone do with a leaked secret key?

Read identity data. A secret key can call GET /api/kyc/verifications/:id for the full result (biodata, the plaintext ID number, the facial match) and GET /api/kyc/verifications/:id/media/:kind for the captured selfie, document images and liveness video. It can also call every Risk Intelligence endpoint.

Treat a secret key like a database password. A leak is a data-exposure incident for your organisation, and your own incident process and legal obligations apply. Those differ by jurisdiction, and this article is general information rather than legal advice.

Where should each key sit in a typical integration?

Here is a common shape for an app that onboards customers:

  1. The app mounts the Myaza SDK with a publishable key. The applicant completes consent, capture and liveness, and the SDK submits the verification.

  2. The app shows a "we are checking your details" screen. If it needs to show progress, it polls the minimal status with the publishable key.

  3. Your backend receives the signed webhook for the verification.

  4. Your backend calls the full result with a secret key, updates the customer record and decides what the app shows next.

The applicant's identity data travels from the device to Myaza and from Myaza to your server. It never needs to come back to the device, and with this layout it cannot, because the only key on the device is not allowed to fetch it.

A frequent mistake is to shortcut step 4 by calling the result endpoint from the app "just for the success screen". That requires a secret key on the device, which defeats the whole design. Send the app the few facts it needs from your own backend instead.

How do environments interact with keys?

Every key belongs to one environment, and data never crosses between them. A pk_test_ key works against Sandbox and a pk_live_ key against Production. Keys, verifications, webhook endpoints and configuration are all scoped to one environment.

Using a key in the wrong place returns 403 environment_mismatch. The practical rule from the documentation is to keep one environment throughout a flow: the SDK's Sandbox mount uses a pk_test_ key, and the backend that reads those results uses an sk_test_ key.

Production keys also depend on your business profile. Until your organisation's business verification (KYB) is approved, production verify and upload requests are rejected with business_not_approved. Sandbox works throughout.

How do you restrict a key before it leaks?

In the dashboard, under Developers → API Keys, each key can carry an allowlist. The type of allowlist follows the key's scope, because that is where each control works.

Key

Restriction

What it stops

What it does not stop

Publishable

Allowed web origins, such as https://app.example.com

A stolen key reused on another website

Requests that send no Origin header, such as native apps and servers

Secret

Allowed IPs or CIDR ranges, such as 203.0.113.0/24

A stolen key used from anywhere other than your servers

Nothing from inside the ranges you listed

Two points follow from that table.

First, an origin allowlist is a browser control. It is worth setting for a web integration, and it does nothing for a mobile app, because a native app sends no Origin. For mobile, the limit on a leaked publishable key is its scope, not the allowlist.

Second, an IP allowlist is the strongest control you have on a secret key, and it is cheap if your backend has fixed egress addresses. List your servers' public egress IPs. If your backend runs on infrastructure without fixed egress, that is worth knowing before you choose where the secret key lives.

An empty allowlist means the key works from anywhere. Leaving it empty is a choice, so make it deliberately.

What should you do when a key leaks?

Work through this in order:

  1. Revoke the key in Developers → API Keys. Revocation is immediate: the next request with that key returns 401.

  2. Create the replacement with the same scope and environment, and deploy it. For a publishable key in a mobile app, that means an app release, so the gap between steps 1 and 2 is real. Plan for it.

  3. Check your audit trail. Key creation and revocation emit the api_key.created and api_key.revoked webhooks, so your security tooling can record who changed what and when.

  4. For a secret key, assess exposure. Establish what the key could reach and for how long, and follow your own incident process.

If the key has not actually leaked and you are rotating on a schedule, reverse steps 1 and 2: create and deploy the new key first, confirm traffic has moved, then revoke the old one. Revoking first would break every request in between.

Which habits keep the blast radius small?

  • One key per service or environment. If your onboarding service and your reporting job share a secret key, revoking it takes both down. Separate keys let you revoke one.

  • Store secret keys in a secrets manager at creation. A secret key is shown only once. Afterwards the dashboard shows only its prefix, so there is nothing to copy later.

  • Keep keys out of repositories. A key committed to a public repository should be treated as leaked, even if the commit is removed.

  • Use _test_ keys in development and CI. Reserve _live_ keys for production traffic.

  • Limit who can manage keys. Creating or revoking a key needs the api_keys:create permission. Members without it see keys read-only.

The rule to apply

When you review an integration, ask one question of every place a key appears: could the person holding this device or reading this bundle use this key to read identity data? If the answer is yes anywhere outside your servers, the design is wrong, whatever else is right about it.

Then check three things: the web publishable key has an origin allowlist, each secret key has an IP allowlist where your infrastructure allows one, and your team has rehearsed the revoke-and-replace steps. The developer documentation covers the endpoints each key reaches, and the security page covers how Myaza Trust handles the data on its side.

Sources

Charles Archibong

About the author

Charles Archibong

Co-founder

Charles Archibong co-founded Myaza Trust. He writes about identity verification, financial technology, and the practical work of building trusted digital services.

Build your product.We'll handle the rest.

Identity and compliance, end to end, built to global standards, priced for founders.

Publishable vs secret API keys for identity verification · Myaza Trust