Skip to content
Customer Pro

Developer

Connect button

Let your own customers connect their Customer Pro account to your app in one click.

On this page

Connect is how another product plugs into Customer Pro. A business using your app clicks one button, sees exactly what you are asking for, picks which of their WhatsApp numbers you may use, and approves. You get a credential scoped to that one business and nothing else — no password, no API key pasted into a settings field, no support ticket.

Register your application #

  1. Open Developer → Applications

    Register the application that will be connecting. You name it, and that name is what your customers see on the consent screen.
  2. List your redirect URIs

    The addresses we may send an authorization code to. They are matched by exact string equality — no wildcards, no subdomain matching. Add every environment you will use, including localhost while you build.
  3. Choose the scopes you may ask for

    A scope you do not register is one you can never request. Ask for the fewest you need; the consent screen shows each one in plain language, and a long list costs you approvals.
  4. Copy the client secret

    It is shown once, at registration. It is what proves the code exchange is really you, so it lives on your server and nowhere near a browser.

An application registered by an organization can only be approved by that organization, which is what makes registration self-service and safe. To be connectable by any business, an application has to be verified by hand — get in touch once yours is working.

Build the authorization URL on your server #

Two of its parameters must never exist in a browser: the PKCE verifier you keep to redeem the code, and the state you check on the way back. So your server builds the URL and renders it into the page.

$verifier = rtrim(strtr(base64_encode(random_bytes(64)), '+/', '-_'), '=');
$state = bin2hex(random_bytes(16));

session(['cp_verifier' => $verifier, 'cp_state' => $state]);

$authorizeUrl = 'https://www.customerpro.in/connect/authorize?'.http_build_query([
    'client_id' => config('services.customerpro.client_id'),
    'redirect_uri' => 'https://your-app.example/integrations/customerpro/callback',
    'scope' => 'messages.send templates.read',
    'state' => $state,
    'code_challenge' => rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '='),
    'code_challenge_method' => 'S256',
]);

Drop in the button #

One script tag and one button. The script opens the consent screen in a popup, so your customer never leaves your page, and hands the authorization code to the callback route you already have.

<script src="https://www.customerpro.in/connect/customerpro.js" defer></script>

<button data-customerpro-connect
        data-authorize-url="{{ $authorizeUrl }}">Connect Customer Pro</button>

That is the whole browser-side integration. When the business approves, the popup posts the result back to your page and the script navigates to your redirect_uri with code and state appended — the same request your callback would have received from a plain redirect.

Field What it does
data-authorize-url Required. The URL your server built above. The script appends display=popup to it.
data-redirect-uri Optional. Where to send the result, if not the redirect_uri already inside the authorize URL.
data-unstyled Optional. Suppresses the default button styling so your own CSS applies.

If you would rather handle the code yourself — a single-page app that should not reload — cancel the event the button fires:

button.addEventListener('customerpro:connect', function (event) {
    event.preventDefault();
    // event.detail is { code, state, error }
});

Exchange the code for credentials #

Your callback receives code and state. Check the state matches the one you stored, then exchange the code from your server, within 60 seconds, exactly once.

POST https://www.customerpro.in/connect/token

{
  "code": "...",
  "code_verifier": "the verifier you stored",
  "client_id": "your client id",
  "client_secret": "your client secret"
}

The response carries the connection's own client_id and client_secret — one pair per business — plus their organization, the WhatsApp number they chose, the scopes they granted and their plan. Store the secret: it is shown once and cannot be retrieved afterwards. Those are the credentials you send to the REST API from then on.

Connected, but the plan cannot use it #

This is the one that catches every integration, so handle it before you ship. A successful handshake proves the business authorized you. It does not prove their plan sells what you are about to call — consent is theirs to give, API access is something they buy. The two are separate on purpose.

So your very first call after a brand-new connection can answer 403 with nothing wrong anywhere:

{
  "error": "This organization's plan does not include API access.",
  "code": "plan_upgrade_required",
  "resource": "api_access",
  "upgrade_url": "https://www.customerpro.in/subscription"
}
  1. Keep the credential

    It is real and it stays valid. This is not a failed connection and re-running the handshake will not change the answer.
  2. Do not retry

    Nothing about waiting helps. Only the business moving to a plan that includes API access does.
  3. Tell the business, not yourself

    They are the only one who can fix it. Show them the message and link them to the upgrade_url in the body.
  4. Find out immediately

    The plan check runs at authentication, so every endpoint refuses identically — you cannot call /me to test first, the 403 body is the answer. Make that /me call right after the exchange anyway, so you can say this while the business is still looking at your connect screen.

When it does not work #

Field What it does
The error page says the redirect address does not match Redirect URIs are compared by exact string equality — no wildcards, no subdomain matching, and a trailing slash counts. Copy the URI out of your authorize URL and paste it into the application's registered list verbatim.
invalid_grant on the exchange Deliberately says nothing about which check failed. In order of likelihood: the PKCE encoding (unpadded base64url of the raw digest, not a hex string), a code older than 60 seconds, a code already exchanged once, or the wrong client_secret. The code is burned even by a failed attempt, so start the handshake again rather than retrying the exchange.
The consent screen says this application has not been approved yet The application has no owning organization, which makes it a platform application any business could approve — so it has to be verified by hand first. An application registered by an organization and approved by that same organization never hits this.
403 plan_upgrade_required The business's plan does not include API access — see above. Not a bug in your integration.
403 plan_limit_exceeded Read resource before deciding what to do: api_calls is a daily ceiling that resets at midnight, messages is a monthly allowance that resets on the 1st. Treating one as the other means telling the business to wait weeks for something that clears overnight.
401 after it was working The connection ended — the business deleted the key, you revoked it, or a reconnect superseded it. Treat as disconnected and prompt them to reconnect. If you have just handled a reconnect, check you stored the new secret: a superseded key answers 401 too.
Nothing happens when the button is clicked Open the console. The script logs when data-authorize-url is missing, and a popup the browser refuses falls back to a full-page redirect rather than failing silently.

Disconnecting #

A business can end a connection at any time by deleting its key on Developer → API keys; your next call answers 401, which means disconnected, not broken. Stop retrying and prompt them to reconnect.

When you are the one ending it, say so: DELETE /api/v1/me hands the credential back and marks the connection disconnected on the business's screen. To end every connection your application holds — winding the integration down, or a leaked secret — use POST /api/v1/applications/{client_id}/revoke-connections. Deleting an application does not revoke its connections.

Let your coding agent build it #

The complete handshake is packaged as a skill file. Drop it into your own project and your agent writes the integration without anyone reading this page:

mkdir -p .claude/skills/customerpro-connect
curl -sL https://www.customerpro.in/connect/skill.md -o .claude/skills/customerpro-connect/SKILL.md

Where to go next #

Frequently asked

What happens if the browser blocks the popup?
The button falls back to a full-page redirect to the same authorization URL. The handshake completes either way, so a blocked popup costs you a page load, not a connection.
Does the button ever see my application secret?
No. Your server builds the authorization URL and exchanges the code for credentials. The script only relays the one-time authorization code back to your callback, and that code is worthless without your secret and PKCE verifier.