Your product has contacts in it. Maybe they are leads in a CRM, signups in a waitlist tool, or recipients in a newsletter app. Sooner or later a customer asks the obvious question: can you tell me which of these addresses will bounce?
You could sign up for a verification API, put your own key on the server, and resell the checks. That means you buy credits in advance, you decide how to pass the cost on, and every heavy user becomes a line on your bill. The other common shortcut is worse: a settings field that asks customers to paste their own API key.
RocketVerifier Connect is a third option. Your customer clicks a Connect RocketVerifier button, approves your app on a consent screen, and your app verifies their contacts on their own credits. This guide walks through the whole integration, from registering the app to handling a disconnect.
Why OAuth beats a pasted API key
A pasted API key works, but it puts the wrong party in charge of a secret. The user copies a long-lived credential out of one product and into another, where it usually has full access to their account. They cannot tell what your app does with it, and the only way to cut you off is to delete the key everywhere it is used.
OAuth 2.0 (RFC 6749) replaces that with a consent step. The user sees your app’s name, the exact permissions you are asking for, and which account will pay. Your app gets a token that only does what was approved, expires after an hour, and can be revoked from either side without touching anything else.
For you, the money moves too. Every verification made with a Connect token is billed to the account the user connected, at their price, with the same rules as the dashboard: 1 credit per check, and unknown results are never charged. Building and running the integration costs you nothing. We compare the two approaches in more depth in API key vs OAuth for an email verification integration.
How the Connect flow works
The flow is the authorization code grant with PKCE (RFC 7636), the pattern the OAuth security best current practice (RFC 9700) recommends for every kind of client, including server apps that also hold a secret.
-
Step 1 · Your app
User clicks Connect RocketVerifier
Your server makes a state value and a PKCE pair, stores them in the session, and redirects the browser.
GET app.rocketverifier.com/oauth/authorize?client_id=rvapp_...&code_challenge=... -
Step 2 · RocketVerifier
User signs in and clicks Allow
The consent screen lists the scopes you asked for. The user picks a personal or organization account and can set a monthly credit cap.
302 your redirect_uri?code=...&state=... -
Step 3 · Your server
Exchange the code, then verify
Check state, trade the code for tokens, and call the API. Each verification is billed to the account the user connected.
POST /api/oauth/token→ rv_at_... (1 hour)POST api.rocketverifier.com/v1/verify
After that, refresh the access token with the rv_rt_ refresh token. It rotates on every use, so save the new one each time.
Three hosts are involved. Your app starts the flow and handles the callback. app.rocketverifier.com runs the consent screen and the token endpoint. api.rocketverifier.com is the same verification API you would call with a key, and it accepts the rv_at_ access token in the same Authorization: Bearer header.
Step 1: register your app
Open Dashboard, Developer apps, New app. You need a name, a homepage URL and at least one redirect URI. Redirect URIs must match character for character, so register http://localhost:3000/callback for development and your production callback separately.
You get two values. The client ID (rvapp_...) is public. The client secret (rv_cs_...) is shown once and belongs on your server only. Pick scopes at the same time:
Scope sets for common features
- Verify each contact as it is created: verify:single and account:read.
- Clean an imported list in one job: verify:bulk, jobs:read and account:read.
- Show the remaining balance in your UI: add credits:read.
- Get a webhook when a bulk job finishes instead of polling: add webhooks:manage.
Ask for less rather than more. The consent screen lists every scope in plain language, and users are more likely to click Allow on a short list. The scopes reference has the full table.
Step 2: send the user to the consent screen
When the user clicks Connect, your server creates a random state value and a PKCE verifier, stores both in the user’s session, and redirects to the authorize URL. The official SDKs do this in one call. In Node.js with the @rocketverifier/sdk package:
import RocketVerifier, { OAuth, OAuthError } from '@rocketverifier/sdk';
const oauth = new OAuth({
clientId: process.env.RV_CLIENT_ID, // rvapp_...
clientSecret: process.env.RV_CLIENT_SECRET, // rv_cs_..., server only
redirectUri: process.env.RV_REDIRECT_URI,
});
app.get('/rocketverifier/connect', (req, res) => {
const { url, state, codeVerifier } = oauth.getAuthorizeUrl({
scopes: ['verify:single', 'account:read'],
});
req.session.rvOauth = { state, codeVerifier };
res.redirect(url);
});
On the RocketVerifier side, the user signs in, chooses their personal account or an organization they administer, can set a monthly credit cap for your app, and clicks Allow. If they click Cancel, your callback receives error=access_denied instead of a code. Show the Connect button again with a short note; it is not an error worth logging.
Step 3: exchange the code and store the tokens
The callback checks state first. A mismatch means the request did not start in this browser session, so stop there. Then trade the code for tokens. The code is single use and expires after 60 seconds.
app.get('/rocketverifier/callback', async (req, res) => {
const saved = req.session.rvOauth;
delete req.session.rvOauth;
if (req.query.error) return res.redirect('/settings?rv=cancelled');
if (!saved || req.query.state !== saved.state) return res.status(400).send('Invalid state');
const tokens = await oauth.exchangeCode({ code: String(req.query.code), codeVerifier: saved.codeVerifier });
await db.rvConnections.upsert(req.user.id, encrypt(tokens));
res.redirect('/settings?rv=connected');
});
Store tokens server side, encrypted at rest, keyed by your user. Never put them in localStorage or a readable cookie. Read the scope field in the response too: it lists what the user actually granted, which can be narrower than what you asked for.
Now call GET /v1/me and show the result in your settings page, for example “Connected as jane@acme.com, 12,400 credits.” It tells the user which account pays, which is the question they will have.
Step 4: verify on the user’s credits
With tokens stored, build a client per request. The SDK refreshes an expired access token for you and hands you the rotated pair to persist:
async function rvFor(userId) {
const tokens = decrypt(await db.rvConnections.get(userId));
return new RocketVerifier({
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
oauth,
onTokenRefresh: (next) => db.rvConnections.upsert(userId, encrypt(next)),
});
}
const rv = await rvFor(req.user.id);
const result = await rv.verify(contact.email);
// result.status: deliverable | undeliverable | risky | unknown
The response is identical to an API key call: status, a precise sub_status such as mailbox_not_found or catch_all, a 0 to 100 score, and did_you_mean for typos. For lists, start a bulk job with up to 100,000 addresses and read the results when it finishes. The recipe for verifying contacts inside your app has complete Node.js and Python versions of both.
Python works the same way with the rocketverifier package: OAuth(...).get_authorize_url(scopes=[...]), exchange_code(...), then RocketVerifier(access_token=..., refresh_token=..., oauth=oauth, on_token_refresh=save).
Register your app and connect your own account
New apps work immediately. Build against your own RocketVerifier account, then submit for review when you are ready to be listed.
Step 5: handle disconnects and errors
A connection can end for several reasons: the user clicks Disconnect in RocketVerifier, the 90 day refresh token expires, an old refresh token is replayed, or the app is suspended. You learn about it in one of two ways.
- A refresh fails with
invalid_grant. The SDK raises it as anOAuthError. Delete the stored tokens and show the Connect button again. - A
grant.revokedwebhook arrives. Set a webhook URL on your app, check theX-RocketVerifier-Signatureheader withverifyWebhookSignature(Node) orverify_webhook_signature(Python), and delete the tokens for that connection.
Handle both, because the webhook can arrive while the user is offline and invalid_grant only shows up on the next call. When the user clicks Disconnect inside your product, call oauth.revoke(refreshToken) so the connection also disappears from their RocketVerifier settings (RFC 7009).
The API errors you need to map to messages are few. A 402 means the user is out of credits or hit the cap they set for your app. A 403 insufficient_scope means you need to ask the user to reconnect with an extra scope. A 429 rate_limited means wait for Retry-After and retry with the same Idempotency-Key, which the SDKs send for you so a retry never charges twice.
Getting listed in the app directory
Your app works for any RocketVerifier user from the moment you create it. The consent screen shows an Unverified app badge until a reviewer approves it. Review checks the basics: a working homepage, a public privacy policy and terms, an accurate name and logo, and that each requested scope maps to a feature. The review guide lists every check.
Approved apps get the Verified badge and are listed in the RocketVerifier app directory, each with its own page that users can find before they ever see your Connect button.
Build it with an AI coding assistant
If you work with Claude Code, Cursor or a similar assistant, give it the integration spec. It is one Markdown file with every endpoint, error code, scope and security check on this page, written for an assistant to follow. Paste the URL and describe the feature, for example “add a Connect RocketVerifier button to our settings page and verify contacts on import.”
For a deeper look at what each verification result means once it comes back, read how email verification works, or test single addresses in the free email verifier.