# Using Custom IO

The Custom Integrated Onboarding allows you to trigger the sign-up experience for clients according to your own development requirements.

## 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 %}

### View Flow Diagram

The following flow diagram shows the general guidelines for a custom Integrated Onboarding implementation.

<figure><img src="/files/KIt7i2F3M1ydUSEOQBd0" alt=""><figcaption></figcaption></figure>

If you have multiple 360dialog Partner Hubs (one for Partner Payment and one for Direct Payment, for example), you can create different buttons for each Hub and show them to users accordingly.
{% endstep %}

{% step %}

### Open Onboarding Pop-Up

Integrated Onboarding is initiated from the `/permissions` screen. [/permissions screen details can be found here.](/partner/onboarding/integrated-onboarding.md#permission-screen-direct-payment-only)

{% 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" %}
The code block below shows an example including explanations. Integrated Onboarding flow can be initiated by calling the function `open360DialogPopup(window.location.origin)`.

```javascript
// `processParams` function retrieves the current URL search parameters and posts them to the parent window.
// If there is an `opener` window, the function posts the parameters to it and closes the current window.
function processParams() {
  const params = window.location.search; // retrieve the current URL search parameters

  // Check if there is an opener window
  if (window.opener) {
    window.opener.postMessage(params); // post the parameters to the opener window
    window.close(); // close the current window
  }
}

// `window.onload` event is used to trigger the execution of the `processParams` function
// when the page has finished loading.
window.onload = function() {
  processParams();
};

// `open360DialogPopup` function opens a new window with the specified URL and options
// and adds a message event listener to the current window.
function open360DialogPopup(baseUrl) {
  window.removeEventListener("message", receiveMessage); // remove any existing message event listeners

  // Window options to be used in opening the new window
  const windowFeatures = "toolbar=no, menubar=no, width=600, height=900, top=100, left=100";
  const partnerId = "yourPartnerId";
  const redirectUrl = "yourRedirectUrl"; // additional redirect if needed - if you don't want to use your 
  // previously set partner redirect

  // Open the new window with the specified URL
  open(
    "https://hub.360dialog.com/dashboard/app/" + partnerId + "/permissions?redirect_url=" + redirectUrl,
    "integratedOnboardingWindow",
    windowFeatures
  );

  // Add a message event listener to the current window
  window.addEventListener("message", (event) => receiveMessage(event, baseUrl), false);
}

// `receiveMessage` function is the callback function that is executed when the message event is triggered.
// It retrieves the data from the event, sets it as the search parameters of the current URL,
// and returns if the origin of the event is not the same as the `baseUrl` or the type of `event.data` is an object.
const receiveMessage = (event, baseUrl) => {
  // Check if the event origin is not the same as `baseUrl` or `event.data` is an object.
  if (event.origin != baseUrl || typeof event.data === "object") {
    return;
  }
  const { data } = event; // retrieve the data from the event
  const redirectUrl = `${data}`; // create a redirect URL from the data
  window.location.search = redirectUrl; // set the redirect URL as the search parameters of the current URL
};
```

{% endtab %}

{% tab title="With IO Signature" %}
Add `io_signature`, `io_timestamp` query parameters into the onboarding link. Add a `fetchIoSignature` function on the frontend to get the io\_signature and io\_timestamp from the partner server.

The code example below expects a REST endpoint on the server, [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
/**
 * 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";

// `processParams` function retrieves the current URL search parameters and posts them to the parent window.
// If there is an `opener` window, the function posts the parameters to it and closes the current window.
function processParams() {
	const params = window.location.search; // retrieve the current URL search parameters

	// Check if there is an opener window
	if (window.opener) {
		window.opener.postMessage(params); // post the parameters to the opener window
		window.close(); // close the current window
	}
}

// `window.onload` event is used to trigger the execution of the `processParams` function
// when the page has finished loading.
window.onload = function () {
	processParams();
};

/**
 * An example function for fetching IO signature and timestamp from
 * the server.
 *
 * You can replace this function with a frontend library solution like
 * TanStack Query for simplified error and loading handling,
 * as well as invalidation.
 */
function fetchIoSignature() {
	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();
		return { signature: payload.signature, timestamp: payload.timestamp };
	});
}

// `open360DialogPopup` function opens a new window with the specified URL and options
// and adds a message event listener to the current window.
function open360DialogPopup(baseUrl) {
	window.removeEventListener("message", receiveMessage); // remove any existing message event listeners

	// Window options to be used in opening the new window
	const windowFeatures =
		"toolbar=no, menubar=no, width=600, height=900, top=100, left=100";
	const partnerId = "yourPartnerId";
	const redirectUrl = "yourRedirectUrl"; // additional redirect if needed - if you don't want to use your
	// previously set partner redirect

	fetchIoSignature().then(({ signature, timestamp }) => {
		// Open the new window with the specified URL
		open(
			"https://hub.360dialog.com/dashboard/app/" +
				partnerId +
				"/permissions?redirect_url=" +
				redirectUrl +
				"io_signature=" +
				signature +
				"&io_timestamp=" +
				timestamp,
			"integratedOnboardingWindow",
			windowFeatures,
		);
	});

	// Add a message event listener to the current window
	window.addEventListener(
		"message",
		(event) => receiveMessage(event, baseUrl),
		false,
	);
}

// `receiveMessage` function is the callback function that is executed when the message event is triggered.
// It retrieves the data from the event, sets it as the search parameters of the current URL,
// and returns if the origin of the event is not the same as the `baseUrl` or the type of `event.data` is an object.
const receiveMessage = (event, baseUrl) => {
	// Check if the event origin is not the same as `baseUrl` or `event.data` is an object.
	if (event.origin != baseUrl || typeof event.data === "object") {
		return;
	}
	const { data } = event; // retrieve the data from the event
	const redirectUrl = `${data}`; // create a redirect URL from the data
	window.location.search = redirectUrl; // set the redirect URL as the search parameters of the current URL
};

```

{% 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="259.28125">Parameter in query</th><th>Description</th></tr></thead><tbody><tr><td>client=<code>&#x3C;client-id></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>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><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>

[See the full list of possible URL Parameters here.](/partner/onboarding/integrated-onboarding.md#url-query-parameters)

Usually the newly opened popup window should close automatically if the [Redirect URL ](#set-redirect-url)matches the calling window’s URL. It will pass the query parameters to the calling window, where these can be retrieved by using the `addEventListener()` method together with the `Window` target:

```java
window.addEventListener(
      "message",
      (event) => {
        const { data } = event;
        const queryString = `${data}`;        
        console.log(queryString);        
        // ?client=oaY9LLfUCL&channels=[y9MiLoCH]
      }, false
    );
```

To retrieve the parameters from the query string, e.g. `?client=oaY9LLfUCL&channels=[y9MiLoCH]`, the browser’s `get()` method of the [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) interface can be used.

```java
let params = new URLSearchParams(queryString);
let channels = params.get("channels");
console.log(channels)
// [y9MiLoCH]
console.log(client)
// oaY9LLfUCL
```

If the newly opened popup window won't close after the redirect call (if redirect is the same as parent window URL), you can add a function that is executed on `window.onload`, ensuring that the parameters are processed only after the page has finished loading:

```java
function processParams() {
  const params = window.location.search;
  if (window.opener) {
    window.opener.postMessage(params);
    window.close();
  }
}
window.onload = function() {
  processParams();
}
```

{% 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_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/using-custom-io.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.
