> ## Documentation Index
> Fetch the complete documentation index at: https://www.text.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate chat summaries and sync them with HubSpot contact timelines

> Run a Go webhook service that automatically generates chat summaries and synchronizes them with your HubSpot contacts after you end the chat.

This guide is for teams that use [HubSpot](https://www.hubspot.com/) as their CRM and want a self-hosted way to add Text conversation summaries to contact timelines without sending chat transcripts to a separate AI provider.

By the end of this guide, the service receives summaries directly from Text, creates or updates HubSpot contacts by customer email, and adds the chat summary to the matching contact timeline.

## Prerequisites

* A [Text account](https://accounts.livechat.com/signup?landing_page=https%3A%2F%2Fwww.text.com%2F\&client_id=fc7bf1555adb6d3d2a441f10fd5b6eb5\&redirect_uri=https%3A%2F%2Fwww.text.com%2Fapp\&response_type=token\&source_type=website\&source_id=header-signup\&source_url=https%3A%2F%2Fwww.text.com%2F) with API access permission.
* A [HubSpot](https://www.hubspot.com/) account with permission to create and install an app.
* [Docker with Docker Compose v2](https://docs.docker.com/get-started/get-docker/).
* [`cloudflared`](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) to expose the local service through a Cloudflare Quick Tunnel. You don’t need it if the service already has a public HTTPS address.
* [Git](https://git-scm.com/downloads) to clone the sample repository.

<GitHub.Repo repo="livechat/text-hubspot-contact-integration" variant="flat" />

## Set up the integration

This guide runs a webhook service between Text and HubSpot. When a conversation closes, Text sends a [`chat_deactivated` webhook](/docs/api/webhooks/v3.6/#chat_deactivated). The service retrieves the customer and Text-generated summary, creates or updates the matching HubSpot contact by email, then adds the summary to its timeline.

<Steps>
  <Step title="Create the Text API access">
    In Text, go to **Settings → API access → [Personal access tokens](https://www.text.com/app/settings/integrations/api-access/personal-access-tokens)**. Create a personal access token with the following [scopes](/docs/authentication/scopes):

    * `chats--all:rw` — to read chats and request summaries in any group, or `chats--access:rw` to limit access to groups that the token owner is a member of.
    * `customers:ro` — to read the customer's email and name.
    * `webhooks.configuration:rw` — to register the webhook.

    Copy the Base64-encoded token value that Text displays — this is your combined `account_id:personal_access_token`.

    Go to **Settings → API access → [OAuth clients](https://www.text.com/app/settings/integrations/api-access/oauth-clients)**, create an OAuth client for the integration, and copy its client ID. You will register the webhooks for this OAuth client.

    The personal access token authorizes the Text API calls used by the service and webhook setup. The OAuth client ID identifies the integration that owns the registered webhook.

    <Warning>
      Treat the personal access token like a password. Store it in a secrets
      manager for a deployed service and never publicly commit it.
    </Warning>
  </Step>

  <Step title="Create the HubSpot API access">
    Follow HubSpot's [app creation guide](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/create-an-app) to create and upload an app. Choose private distribution and static authentication, then configure these [scopes](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/authentication/scopes):

    * `crm.objects.contacts.read` to find a contact by email.
    * `crm.objects.contacts.write` to create or update the contact.
    * `crm.objects.notes.write` to create the associated note.

    After uploading the app, [install it with a static token](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/manage-apps-in-hubspot#install-an-app-with-a-static-token) in the HubSpot account you want to connect. Copy the generated access token. You will set it as `HUBSPOT_ACCESS_TOKEN` when configuring the service.

    Static authentication connects the sample to one HubSpot account. Use OAuth instead when your app needs to connect multiple HubSpot accounts.

    The service uses the [HubSpot contacts API](https://developers.hubspot.com/docs/api-reference/latest/crm/objects/contacts/guide) for contact management and the [HubSpot notes API](https://developers.hubspot.com/docs/api-reference/latest/crm/activities/notes/guide) to add summaries to contact timelines.
  </Step>

  <Step title="Clone and configure the service">
    Clone the repository and enter its directory:

    ```shell theme={null}
    git clone https://github.com/livechat/text-hubspot-contact-integration.git
    cd text-hubspot-contact-integration
    ```

    Create a `.env` file with the following values:

    ```shell theme={null}
    TEXT_BASIC_AUTH=<base64-account-id-and-personal-access-token>
    TEXT_WEBHOOK_SECRET=<long-random-secret>
    HUBSPOT_ACCESS_TOKEN=<hubspot-static-access-token>
    TEXT_OWNER_CLIENT_ID=<text-client-id>
    WEBHOOK_PUBLIC_URL=<public-https-base-url>
    ```

    Generate a webhook secret with a command such as:

    ```shell theme={null}
    openssl rand -hex 32
    ```

    Set the generated value as `TEXT_WEBHOOK_SECRET`.

    The service listens locally at `http://localhost:8080`, but Text needs a public HTTPS address to deliver webhook events. For local testing, a free [Cloudflare Quick Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/) gives the local service a temporary public address.

    To start the tunnel, run [`cloudflared`](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) in a separate terminal:

    ```shell theme={null}
    cloudflared tunnel --url http://localhost:8080
    ```

    The command prints a temporary `https://<random-name>.trycloudflare.com` address. Keep the tunnel running while you test. If its address changes after you register the webhook, [unregister the old webhook](/docs/api/configuration/v3.6/webhooks/unregister-webhook) before registering it again.

    Set `WEBHOOK_PUBLIC_URL` to the tunnel address without a trailing slash. For a deployed service, skip the tunnel and use the service's public HTTPS address instead.

    The repository excludes `.env` from Git. Keep the file private because it contains credentials for both systems.
  </Step>

  <Step title="Register the Text webhook">
    Load the values from `.env` into your terminal session:

    ```shell theme={null}
    set -a
    . ./.env
    set +a
    ```

    Register a license-level `chat_deactivated` webhook with the [`register_webhook`](/docs/api/configuration/v3.6/webhooks/register-webhook) method:

    ```shell theme={null}
    curl --fail-with-body \
      --request POST 'https://api.livechatinc.com/v3.6/configuration/action/register_webhook' \
      --header "Authorization: Basic ${TEXT_BASIC_AUTH}" \
      --header 'Content-Type: application/json' \
      --data "{
        \"action\": \"chat_deactivated\",
        \"url\": \"${WEBHOOK_PUBLIC_URL}/webhooks/text\",
        \"secret_key\": \"${TEXT_WEBHOOK_SECRET}\",
        \"owner_client_id\": \"${TEXT_OWNER_CLIENT_ID}\",
        \"type\": \"license\"
      }"
    ```

    The response contains the webhook ID.

    Run the registration once. To check the registrations owned by a specific OAuth client ID, use [List webhooks](/docs/api/configuration/v3.6/webhooks/list-webhooks).

    <Warning>
      After you register the webhook, the service copies customer identity and
      conversation summaries to HubSpot. Confirm that this data flow meets your
      privacy, consent, retention, and deletion requirements before you activate
      it for customer conversations.
    </Warning>
  </Step>

  <Step title="Start the service">
    Build and start the container:

    ```shell theme={null}
    docker compose up -d --build
    ```

    Confirm that the service is healthy:

    ```shell theme={null}
    curl --fail --include http://localhost:8080/healthz
    ```

    A healthy service returns HTTP `204`.

    Follow its logs while you test the integration:

    ```shell theme={null}
    docker compose logs --follow contact-sync
    ```
  </Step>

  <Step title="Verify the HubSpot contact and note">
    The service matches HubSpot contacts by customer email and skips chats without one. It maps the Text customer name to HubSpot `firstname` and `lastname` by splitting at the first whitespace.

    To test the service, start a chat as a customer with an email address, exchange at least one message, then close the chat.

    The service log should contain `synced Text chat` with the Text chat ID, thread ID, and HubSpot contact ID. In HubSpot, open the contact with the same email address. Its activity timeline should contain a note with the summary title and bullet points and the chat details.
  </Step>
</Steps>

## Troubleshooting

| Symptom                            | Likely cause                                                                                | What to check                                                                        |
| ---------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| The container exits during startup | A required environment value is missing, or `TEXT_BASIC_AUTH` is not a Base64-encoded value | Check `TEXT_BASIC_AUTH`, `TEXT_WEBHOOK_SECRET`, and `HUBSPOT_ACCESS_TOKEN` in `.env` |
| Text cannot deliver the webhook    | The URL is not publicly reachable over HTTPS, or the tunnel URL changed                     | Check `WEBHOOK_PUBLIC_URL`, the `/webhooks/text` path, and your tunnel or deployment |
| The webhook returns HTTP `401`     | The registered `secret_key` differs from `TEXT_WEBHOOK_SECRET`                              | Register the webhook and run the service with the same secret                        |
| The log says the sync was skipped  | The Text customer record has no email                                                       | Add an email to the customer record and test with another closed chat                |

## Prepare the design for production

The sample demonstrates the integration flow, but it does not protect every sync from service restarts, temporary API failures, or repeated webhook deliveries.

Before deploying the service, adapt the sample in three areas.

<Accordion title="Make sync delivery reliable">
  The sample confirms receipt of a webhook before the sync finishes. It does not
  save unfinished work or remember which threads it has processed, so a restart
  can lose a sync and a repeated webhook can create a duplicate note.

  Before deploying:

  * Save each sync job in a durable queue before confirming receipt of the webhook.
  * Retry temporary Text and HubSpot failures, and alert your team when a job still fails.
  * Record processed Text thread IDs so repeated webhook deliveries do not create another note.
  * Continue delayed summary work after receiving the [`thread_summary_set`](/docs/api/webhooks/v3.6/#thread_summary_set) webhook instead of relying on the sample's 60-second polling window.
</Accordion>

<Accordion title="Define how HubSpot contacts are updated">
  The sample matches contacts by email, updates name fields from Text, and creates
  one note for every closed thread. Those choices may not match how your team
  manages customer records in HubSpot.

  Before deploying:

  * Decide whether Text or HubSpot owns each contact field.
  * Store a Text customer ID to HubSpot contact ID mapping if email addresses can change.
  * Decide how to handle customers without an email address or with duplicate HubSpot contacts.
  * Choose whether HubSpot receives one note per thread or one note per chat.
</Accordion>

<Accordion title="Protect customer data in both systems">
  The sample exposes a webhook endpoint and copies customer identity and
  conversation summaries from Text to HubSpot. It does not define how the copied
  data is retained or deleted.

  Before deploying:

  * Run the service behind a stable HTTPS endpoint with health checks and monitoring.
  * Store credentials in a secrets manager and rotate the webhook secret.
  * Copy only the customer data you need and restrict access to it.
  * Define retention and deletion behavior for the copied data in both Text and HubSpot.
</Accordion>
