# IO Signature

IO Signature is an **optional** feature that prevents clients from onboarding phone numbers without authorization from the partner platform.

**We recommend partners use IO signatures to enhance their platform security.**

Implementing IO signatures requires adding a new server endpoint.

## IO Signature Implementation

{% hint style="danger" %}
**Server-only**

IO signature generation must only be done server-side.

Do not generate the signature on the frontend - this will leak the platform secret, allowing for unauthorized phone number onboarding attempts, as well as risking webhook URL's security.

[If the platform secret has leaked, regenerate it on the 360Dialog Hub by following instructions here.](/partner/partner-hub/integration.md#generate-platform-secret)
{% endhint %}

{% stepper %}
{% step %}

### Get Platform Secret

[Get an existing platform secret, or generate a new one by following the instructions here.](/partner/partner-hub/integration.md#generate-platform-secret)
{% endstep %}

{% step %}

### Prepare String to Sign

Prepare the string to sign with the platform secret. The string is the partner's ID and timestamp now in **UNIX seconds** separated by a pipe character. Example:

`{partnerId}|{timestampSeconds}`

If the partner's ID is `aAbBcCPA`, and the timestamp is `1775653748`, the string to sign would look like:

`aAbBcCPA|1775653748`

{% hint style="info" %}
**Avoid caching**

Caching this string preparation step, or the signing step below, is not recommended (e.g. generating every N minutes).

[Replay protection prevents reusing the same signature across multiple onboarding attempts.](#link-reuse)
{% endhint %}
{% endstep %}

{% step %}

### Compute Signature

The signature must be computed using **HMAC-SHA512**. Sign the string prepared in Step 2 using the platform secret acquired in Step 1.

Below are code samples for Python, Node.js, and PHP.

{% tabs %}
{% tab title="Python" %}

```python
# io_signature.py

import os
import time
import hmac
import hashlib

partner_id = "Your partner ID here"

# Platform secret should be passed as an environment variable as best practice
secret = os.getenv("PLATFORM_SECRET")

def generate_signature(partner_id: str, secret: str) -> dict:
    if secret is None or secret.trim() == "":
        raise Exception("ERROR: No platform secret provided")
        
    timestamp = int(time.time())
    message = f"{partner_id}|{timestamp}"
    
    signature = hmac.new(
        secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha512
    ).hexdigest()

    return {
        "timestamp": timestamp,
        "signature": signature,
    }
```

{% endtab %}

{% tab title="Node.js (ES6)" %}

```javascript
// io_signature.js

import crypto from "crypto";

const partnerId = "Your partner ID here";

// Partner secret should be passed as an environment variable as best practice
const secret = process.env.PLATFORM_SECRET;

export function generateSignature() {
	if (!secret || !secret.trim()) {
		throw Error("ERROR: No platform secret provided");
	};
	
	const timestamp = Math.floor(Date.now() / 1000);
	const message = `${partnerId}|${timestamp}`;
	const signature = crypto
		.createHmac("sha512", secret)
		.update(message)
		.digest("hex");
	return { signature, timestamp };
}
```

{% endtab %}

{% tab title="PHP" %}

```php
// io_signature.php

<?php

function generateSignature(): array
{
    $partnerId = "Your partner ID here";

    // Platform secret should be passed as an environment variable as best practice
    $secret = getenv("PLATFORM_SECRET");

    $timestamp = time();
    $message = $partnerId . "|" . $timestamp;

    $signature = hash_hmac("sha512", $message, $secret);

    return [
        "timestamp" => $timestamp,
        "signature" => $signature,
    ];
}
```

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

{% step %}

### Serve Signature and Timestamp

Partner's server must generate the signature and timestamp at each **authenticated** request, and serve them to users before they start onboarding a phone number.

If the app is rendered client-side, the server must have a REST endpoint for requesting a signature and timestamp.

If the app is rendered server-side, the server can also ship the signature and timestamp in the page's HTML.

Below are REST API endpoint samples for Python, Node.js, and PHP, each serving at <mark style="color:$success;">`GET`</mark> `/api/sign_io`.

{% hint style="warning" %}
**Authentication**

These code samples do not cover authentication.

**Make sure to implement authentication into the server, and authenticate all requests made to this endpoint.** Skipping this step breaks security benefits of IO signatures.
{% endhint %}

{% tabs %}
{% tab title="Python" %}

<pre class="language-python"><code class="lang-python"><strong># server.py
</strong>
import os
import time
import hmac
import hashlib
from flask import Flask, jsonify
from io_signature import generate_signature # Sample shown in Step 3

app = Flask(__name__)

@app.get("/api/sign_io")
def signature():
    return jsonify(generate_signature())
</code></pre>

{% endtab %}

{% tab title="Node.js (ES6)" %}

```javascript
// server.js

import express from "express";
import crypto from "crypto";
import dotenv from "dotenv";
import { generateSignature } from "./io_signature.js"; // Sample shown in Step 3

dotenv.config();

const app = express();

app.get("/api/sign_io", (req, res) => {
  res.json(generateSignature());
});

app.listen(3000);
```

{% endtab %}

{% tab title="PHP" %}

<pre class="language-php"><code class="lang-php"><strong>// api/sign_io.php
</strong>
&#x3C;?php

require_once __DIR__ . "/../io_signature.php"; # Sample shown in Step 3

header("Content-Type: application/json");

echo json_encode(generateSignature());
</code></pre>

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

## Enable IO Signature

{% stepper %}
{% step %}

### Open 360Dialog Hub

[Click here to open 360Dialog Hub.](https://hub.360dialog.com/)
{% endstep %}

{% step %}

### Open Integration Page

[Navigate to the Integration page by clicking here.](https://hub.360dialog.com/dashboard/partner/integration)

<figure><img src="/files/eQfxsH7WB6tiHyexiJu3" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Test IO Signature

Get a signature and timestamp from your server's signature endpoint, exampled under [#io-signature-implementation](#io-signature-implementation "mention").

Click **Test** in the UI, then paste the signature and timestamp, and finally click **Test**.

<figure><img src="/files/B17GcNwOtQYdFWNaJOR6" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Toggle **IO Signature**

Enable **IO Signature** by clicking the toggle next to IO Signature if:

* The test in Step 3 was successful,
* The frontend successfully requests a signature from the backend,
* And frontend correctly passes the IO signature + timestamp to the integrated onboarding link.

Enabling this toggle will enforce a valid IO signature + timestamp on all onboarding attempts.

<div align="left"><figure><img src="/files/ozXaRYc4Rl6mrmoiRrc3" alt=""><figcaption></figcaption></figure></div>
{% endstep %}
{% endstepper %}

## IO Security Measures

When IO Signature is enabled, 360Dialog protects against integrated onboarding link staleness and reuse.

### Link Staleness

Signatures older than 24 hours are automatically rejected.

{% hint style="info" %}
**Suggestion**

Store in the frontend when the signature was received. When the user attempts to start onboarding, check if the signature was received more than 24 hours ago; if true, get a new signature.
{% endhint %}

### Link Reuse

Replay protection is active for 48 hours per used signature.

When a signature is first used to attempt integrated onboarding, any further attempts to use the signature are blocked. As a result, users cannot use the same integrated onboarding link to perform multiple onboarding attempts.

{% hint style="info" %}
**Suggestion**

Issue a new signature + timestamp per onboarding attempt.
{% endhint %}


---

# 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/io-signature.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.
