Two teams add email verification in the same week. The first runs an outbound agency and wants every lead checked before it reaches a sequencer. The second sells a form builder and wants its customers to be able to verify their own submissions. Both read the same API docs. They should not build the same thing.
The agency is verifying its own data and paying for it, which is what an API key is for. The form builder is acting for hundreds of separate customers, each of whom should pay for their own checks and be able to switch the integration off. That is the problem OAuth was designed for.
This post lays out the difference in terms of an email verification integration specifically, using RocketVerifier’s API keys and RocketVerifier Connect as the concrete example.
The one question that decides it: whose account pays
Most comparisons of API keys and OAuth start with security. For a verification API, start with billing, because it settles the answer in almost every case.
Every verification costs a credit. With an API key, that credit comes from the account that created the key. If your backend checks addresses for your own business, that is correct, and a key is the simplest thing that works.
With OAuth, the credit comes from the account the user connected. RocketVerifier Connect bills every verification to the user’s account, or to their organization’s pooled credits if they picked one on the consent screen. You pay nothing, and each customer pays their own price under their own plan. If you are building a product where customers verify their own contacts, this is the model that lets you offer verification without reselling it.
- Whose credits
- API key:Yours. Every call bills the account that created the key.
- Connect:The user's, or their organization's. You pay nothing.
- Who holds the secret
- API key:Your server, as rv_live_... in an environment variable.
- Connect:Your server holds tokens per user. The user never sees or pastes a secret.
- Lifetime
- API key:Until you revoke it, or an expiry date you set.
- Connect:rv_at_ access token: 1 hour. rv_rt_ refresh token: 90 days, rotated on every use.
- Permissions
- API key:Key scopes you choose in the dashboard.
- Connect:Scopes the user approves on the consent screen. The grant can be narrower than you asked.
- Spending limits
- API key:A monthly spend cap and IP allowlist per key.
- Connect:A monthly credit cap the user sets for your app when connecting.
- Ending access
- API key:You delete or rotate the key.
- Connect:The user clicks Disconnect, or you revoke. You get a grant.revoked webhook.
- Testing
- API key:rv_test_ sandbox keys, no credits used.
- Connect:No sandbox. Connect your own account; each verification is live.
Use a key when your own backend verifies emails for your own business.
Use Connect when each of your customers brings their own RocketVerifier account.
Why “paste your API key here” is the wrong shortcut
There is a tempting middle path: build your product with an API key field and ask each customer to paste theirs. It avoids the OAuth callback and seems to put the cost on the customer. It causes three problems.
The customer loses control of the secret. A key copied into your settings page now lives in two places. The customer cannot see what you do with it, and to cut off your access they have to delete the key, which breaks anything else that uses it.
You become responsible for a credential you cannot scope. The key carries whatever access the customer gave it, often everything. If your database leaks, you have leaked working credentials to other people’s accounts. OWASP’s secrets management guidance treats long-lived shared secrets as something to minimize for exactly this reason.
Support gets harder. “It stopped working” now has a dozen possible causes, from a rotated key to an expired one to a key from the wrong workspace, and none of them are visible to you.
OAuth replaces the pasted secret with a consent step. The customer sees your app’s name and the exact permissions, approves once, and can disconnect from their own settings at any time. You hold tokens that expire, carry only the approved scopes, and can be revoked from either side (RFC 6749).
What OAuth costs you in code
OAuth is not free. Compared with reading a key from an environment variable, you need:
- A connect route and a callback. Generate
stateand a PKCE pair, redirect to the consent screen, and exchange the returned code for tokens. - Token storage per user. Encrypted, server side, keyed by your user or workspace.
- Refresh handling. RocketVerifier access tokens last one hour. Refresh tokens last 90 days and rotate on every use, so you must save the new one each time and avoid two refreshes racing.
- Disconnect handling. A failed refresh with
invalid_grant, or agrant.revokedwebhook, means the connection is gone. Delete the tokens and show Connect again.
The official Node.js and Python SDKs do most of this. getAuthorizeUrl builds the URL and PKCE pair, exchangeCode trades the code, and the client refreshes expired tokens and gives you the rotated pair to store. A working integration is a few dozen lines on top of the verification code you would write anyway. Our walkthrough of adding email verification to your SaaS with OAuth shows each piece.
Testing: sandbox keys vs live tokens
This is the one place where API keys are easier. RocketVerifier sandbox keys start with rv_test_ and return fixed results for addresses at @sandbox.rocketverifier.com, such as deliverable@ or disposable@, without spending credits. They are ideal for CI.
OAuth tokens have no sandbox mode. Every call with an rv_at_ token is a live verification on the connected account. In practice that means you connect your own RocketVerifier account while developing, verify a handful of real addresses (1 credit each, unknown results free), and mock the SDK client in automated tests.
Security in practice
Neither option is secure by default; each has a short list of things to get right.
For API keys: keep live keys on the server, read them from an environment variable or secret manager, and use the controls RocketVerifier offers per key: an expiry date, a monthly credit spend cap and an IP allowlist. Never call the API from browser code with a live key. If you need verification in a form, the signup form guide covers server endpoints and RocketShield.
For OAuth: use PKCE on every request, compare state before anything else in the callback, keep the client secret off browsers and devices, encrypt stored tokens, and request the fewest scopes your feature needs. Bearer tokens of both kinds go in the Authorization header, never in a URL (RFC 6750).
Pick the path that matches your product
Your own backend: create an API key. Your customers' accounts: register a Connect app. Both start free.
Switching later is cheap
Both credentials call the same endpoints, with the same Authorization: Bearer header, and get the same response shape. A POST /v1/verify returns status, sub_status, score and catch_all_verdict whether it was sent with rv_live_... or rv_at_....
So a common path is to start with an API key while you prove the feature internally, then add Connect when you open it to customers. The code that interprets results does not change; only the code that obtains the credential does. If you want to see what those results mean in detail, start with how email verification works and the glossary.