# Using Connect Button

The 360Dialog Connect Button is a React Node.js package that simplifies **Integrated Onboarding (IO).** The button is customizable and, when clicked, triggers the Integrated Onboarding flow. It also simplifies handling the post-IO-flow redirect.

Partners using a different UI library or framework, or preferring their own implementation, can create their own Integrated Onboarding trigger and redirect consumer by [following this guide.](/partner/onboarding/integrated-onboarding/using-custom-io.md)

The following demo/generator site can be used to practice consuming IO redirects:

* [360Dialog Connect Button demo / generator](https://integrated-onboarding-demo.vercel.app/)
* [Source code on GitHub - 360dialog/integrated-onboarding-demo](https://github.com/360dialog/integrated-onboarding-demo)

## Requirements

* [x] Redirect URL must be set in the 360Dialog Hub. [See instructions here.](/partner/partner-api/overview.md#partner-hub-redirect-url)
* [x] Partner Hub webhook URL must be set. [See instructions here.](/partner/partner-api/overview.md#partner-hub-webhook)

### Direct-Paid Accounts

Clients of [Direct-Paid partner accounts](/partner/get-started/billing-models.md#direct-paid) will see a price detail and payment information page between the **Account Creation** and **Embedded Signup** steps.

The client needs to grant API key permission before the partner can generate an API key for them. Clients can give this permission during the Integrated Onboarding flow (pictured below), or in the 360Dialog Hub after onboarding.

<figure><img src="/files/duhAKlhHt951jrKfMcOd" alt=""><figcaption><p>API key permission request screen shown to clients of Direct-Paid partner accounts</p></figcaption></figure>

## Implementation

{% stepper %}
{% step %}

### **Install Package**

Install the [360dialog-connect-button](https://www.npmjs.com/package/360dialog-connect-button) package from npm:

```bash
npm install 360dialog-connect-button
```

[We recommend reading the package's description on npm to view a full list of the component's props.](https://www.npmjs.com/package/360dialog-connect-button)
{% endstep %}

{% step %}

### Add Button Component

{% hint style="info" %}
**With /** **Without IO Signature**

The button can be configured to use [IO Signature, a new **optional** security feature](/partner/onboarding/integrated-onboarding/io-signature.md) that prevents clients from onboarding phone numbers without authorization from the partner/partner platform.

It requires a server-side implementation, and IO Signature must be enabled in the 360Dialog Hub. [See implementation instructions here.](/partner/onboarding/integrated-onboarding/io-signature.md)

***

Partners preferring a quicker start can use the 360Dialog Connect Button without using the IO Signature feature.

Code examples for both approaches (with and without IO Signature) are shown below.
{% endhint %}

{% tabs %}
{% tab title="Without IO Signature" %}
Import the `ConnectButton` component from the `360dialog-connect-button` package.

Implement the component like in the example below.

```javascript
import { ConnectButton } from '360dialog-connect-button';

function OnboardingPage() {
  const handleCallback = callbackObject => {
    /** 
     * The callback function returns:
     * 1. the client's ID,
     * 2. all channel IDs the partner can generate an API key for,
     * 3. channel IDs the partner can no longer generate an API key for.
     *
     * See Step 3 for details.
     */
    console.log('client ID: ' + callbackObject.client);
    console.log('channel IDs: ' + callbackObject.channels);
    console.log('channel IDs with revoked API key access: ' + callbackObject.revokedChannels || []);
  };
  
  return (
    <div>
      <h1>Connect your WhatsApp Business Account</h1>
      <ConnectButton
        partnerId="YOUR_PARTNER_ID"
        callback={handleCallback}
      />
    </div>
  );
}
```

{% endtab %}

{% tab title="With IO Signature" %}
Import the `ConnectButton` component from the `360dialog-connect-button` package.

Implement the component with `io_signature`, `io_timestamp` props and a `fetchIoSignature` function on the frontend.

The code example below expects a REST endpoint on the server-side, [as shown in the implementation guide here.](/partner/onboarding/integrated-onboarding/io-signature.md#io-signature-implementation)

{% hint style="warning" %}
**Signature invalidation not covered**

An IO Signature becomes invalid:

* after 24 hours,
* or when it has been used once to start IO.

When an IO signature is invalidated, a new one must be acquired from the server before the user can make another IO attempt.

**The example below does not cover signature invalidation.**
{% endhint %}

```javascript
import { ConnectButton } from '360dialog-connect-button';

/**
 * Your (the partner's) server must have an endpoint that returns a fresh IO signature
 * and timestamp.
 *
 * IO signature implementation example shown below:
 * https://docs.360dialog.com/partner/onboarding/integrated-onboarding/io-signature
 */
const MY_SIGNATURE_ENDPOINT = "https://foo.com/api/sign_io";

function OnboardingPage() {
  const [esSignature, setEsSignature] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError = useState(null);
  
  /**
   * An example function for fetching IO signature and timestamp from
   * the server.
   * 
   * You can replace this function and the useStates above, with a solution like
   * TanStack Query for simplified error and loading handling, as well as
   * invalidation.
   */
  const fetchIoSignature = () => {
    if (isLoading) return;
    
    setIsLoading(true);
    setError(null);
    
    fetch(MY_SIGNATURE_ENDPOINT, {
      method: "GET",
      // ... other request parameters your server accepts,
      // like a JWT for authentication.
    })
      .then(async (response) => {
        const payload = await response.json();
        setEsSignature({ signature: payload.signature, timestamp: payload.timestamp });
      })
      .catch((error) => {
        setError(error);
      })
      .finally(() => {
        setIsLoading(false);
      })
  }
  
  const handleCallback = callbackObject => {
    /** 
     * The callback function returns:
     * 1. the client's ID,
     * 2. all channel IDs the partner can generate an API key for,
     * 3. channel IDs the partner can no longer generate an API key for.
     *
     * See Step 3 for details.
     */
     console.log('client ID: ' + callbackObject.client);
     console.log('channel IDs: ' + callbackObject.channels);
     console.log('channel IDs with revoked API key access: ' + callbackObject.revokedChannels || []);
  }
  
  useEffect(() => {
    fetchIoSignature();
  }, []);
  
  return (
    <div>
      <h1>Connect your WhatsApp Business Account</h1>
      {!isLoading && (!isError ?
        <ConnectButton
          partnerId="YOUR_PARTNER_ID"
          callback={handleCallback}
          queryParameters={{
            io_signature: esSignature.signature,
            io_timestamp: esSignature.timestamp,
          }}
        />
        :
        <>
        // Handle signature retrieval error here
        </>
        )
      }
    </div>
  );
}
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Consume Redirect

After completing onboarding, clients are redirected to the partner's configured redirect URL. The following query parameters are added to the redirect URL:

<table><thead><tr><th width="221.6015625">Parameter in query</th><th width="230.6328125">Component callback prop</th><th>Description</th></tr></thead><tbody><tr><td>client=<code>&#x3C;client-id></code></td><td><code>CallbackObject.client</code></td><td>ID of the client who was redirected to the partner's redirect URL.</td></tr><tr><td>channels=<code>[&#x3C;channel-id>,&#x3C;channel-id>]</code></td><td><code>CallbackObject.channels</code></td><td>Comma-separated array of channel IDs (a.k.a. phone numbers) the partner has permission to generate an API key for</td></tr><tr><td>revoked=<code>[&#x3C;channel-id>]</code></td><td><code>CallbackObject.revokedChannels</code></td><td><p><strong>OPTIONAL - may not be present in every redirect.</strong></p><p></p><p>Comma-separated array of channel IDs (a.k.a. phone numbers) the client has revoked partner's API key permissions for</p></td></tr></tbody></table>

The **360Dialog Connect Button** simplifies these queries' parsing and handling if it is present/mounted in the partner's configured redirect URL. The function passed in the component's `callback` property is called after the client is redirected from Integrated Onboarding to the partner's redirect URL page.

If the 360Dialog Connect Button is present and mounted in the redirect URL page, **proceed to the next step.**

If it is not possible to mount the component on the redirect URL page, use the following JS code in the redirect URL page to replicate the 360Dialog Connect Button's handling behavior:

<details>

<summary>Manually handling redirect query parameters</summary>

Manually handle query parameters passed in the redirect URL:

```javascript
function handleCallback() {
  // Get URL parameters
  const params = new URLSearchParams(window.location.search);
  
  // Extract client ID
  const clientId = params.get('client');
  
  if (!clientId) {
    // Client ID is not present, early return
    return;
  }
  
  // Extract channel IDs (format: [channelId1,channelId2])
  const channelsString = params.get('channels');
  const channels = channelsString ? 
    channelsString.replace(/[\[\]]/g, '').split(',') : 
    [];
  
  if (channels.length > 0) {
    // Store the client and channel information
    console.log(`Client ${clientId} granted permission to channels: ${channels}`);
    
    // TODO: Continue with your application flow
    // saveClientData(clientId, channels);
  }
  
  const revokedChannelsString = params.get('revoked');
  const revokedChannels = revokedChannelsString ?
    revokedChannelsString.replace(/[\[\]]/g, '').split(',') : 
    [];
    
  if (revokedChannels.length > 0) {
    console.log(`Client ${clientID} revoked permission for channels: ${revokedChannels}`);
  }
}

// Execute on page load
document.addEventListener('DOMContentLoaded', handleRedirect);
```

Then close the pop-up window (if it hasn't closed automatically):

```javascript
// In your redirect handler
if (window.opener) {
  window.opener.postMessage({ 
    client: clientId, 
    channels: channels 
  }, '*');
  window.close();
}
```

</details>
{% endstep %}

{% step %}

### Handle Webhooks

After completing onboarding, the partner's webhook URL endpoint will receive the following important status events:

* `channel_created`: [Sent when a new WhatsApp channel is created](/partner/onboarding/webhook-events-and-setup/webhook-events-partner-and-messaging-api.md#channel-created)
* `channel_status_running`: [Sent when the channel status changes](/partner/onboarding/webhook-events-and-setup/webhook-events-partner-and-messaging-api.md#channel-running)
* `phone_number_quality_changed`: [Sent when the messaging limit changes](/partner/onboarding/webhook-events-and-setup/webhook-events-partner-and-messaging-api.md#quality-rating-event)

```javascript
// Example Express.js webhook handler
app.post('/webhooks/360dialog', express.json(), (req, res) => {
  const event = req.body;
  
  switch (event.type) {
    case 'channel_created':
      console.log(`New channel created: ${event.payload.channel}`);
      break;
      
    case 'channel_status_running':
      console.log(`Channel ${event.payload.channel} is now active!`);
      break;
      
    case 'phone_number_quality_changed':
      console.log(`Channel ${event.payload.channel} messaging limit: ${event.payload.value}`);
      break;
  }
  
  // Always respond with 200 to acknowledge receipt
  res.status(200).send();
});
```

{% endstep %}

{% step %}

### Generate API Key

When the channel reaches `running` status, create an API key for the newly-onboarded phone number to begin sending messages with this phone number.

```bash
curl -X POST https://hub.360dialog.io/api/v2/partners/channels/{channelId}/api-keys \
  -H "Authorization: Bearer YOUR_PARTNER_TOKEN"
```

Store the returned API key securely - it cannot be retrieved later.

{% hint style="info" %}
**Reminder for** [**Direct-Paid partners**](/partner/get-started/billing-models.md#direct-paid)

Partners on the Direct-Paid billing plan must receive API key permission from their client before being allowed to generate an API key for the client's phone number.

Clients are asked during onboarding if they want to grant API key permission. They can also grant API key permission later in the 360Dialog Hub.

See [Partner Permissions](/partner/partner-hub/api-keys.md) for details.
{% endhint %}
{% endstep %}
{% endstepper %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.360dialog.com/partner/onboarding/integrated-onboarding/connect-button.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
