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

# Building an integration

> A complete example with the Connecta DESK API, from the e-commerce order to the registered contact, the message sent and the card on the CRM board.

This page walks through an integration from start to finish. In the example, an e-commerce store
needs to do three things for every confirmed order: register the contact in Connecta DESK (or update
it, if it already exists), notify the customer over WhatsApp and open a card on the CRM's post-sale
board.

Along the way you will see practices that work for any integration: using upsert instead of create,
finding records by an identifier from your own system and using idempotency on actions the customer
sees.

## The token's scope

Create the token with only the permissions this case uses:

```json theme={null}
{
  "contacts": ["GET", "POST"],
  "messages": ["POST"],
  "crm": ["GET", "POST"],
  "channels": ["GET"]
}
```

The token has no `DELETE` and no `PUT`, because this integration never deletes or edits anything.
If one day it needs to update contacts, add `contacts: PUT` to the token.

<Steps>
  <Step title="Fetch the fixed ids once">
    The outbound channel and the CRM board do not change from one order to the next. Fetch the ids
    once and keep them in configuration:

    ```bash theme={null}
    curl -s --url "$CONNECTA_URL/channels" \
      --header "Authorization: Bearer $CONNECTA_TOKEN" | jq '.data[] | {id, name, status}'

    curl -s --url "$CONNECTA_URL/crm/boards" \
      --header "Authorization: Bearer $CONNECTA_TOKEN" | jq '.data[] | {id, name}'
    ```

    <Warning>
      Do not copy these ids from one environment to another. The "Post-sale" board has one id in
      staging and another in production. Look it up by name in each environment or keep a separate
      configuration for each one.
    </Warning>
  </Step>

  <Step title="Register or update the contact">
    Creating with `POST /contacts` returns an error when the customer has bought before. Use the
    upsert, which creates the contact or updates the existing one by number:

    ```javascript theme={null}
    const { data: contact } = await api('/contacts/upsert', {
      method: 'POST',
      body: JSON.stringify({
        number: order.phone,
        name: order.customerName,
        email: order.email,
        code: String(order.customerId)
      })
    }).then((r) => r.json());
    ```

    <Tip>
      The `code` field stores the customer's identifier **in your system**. With it filled in from
      the start, you can find the contact later with `GET /contacts?code=…`, without depending on
      the phone number, which may change.
    </Tip>
  </Step>

  <Step title="Notify the customer">
    Outside WhatsApp's 24-hour window, the first message must be an approved template:

    ```javascript theme={null}
    const { data: job } = await api('/messages', {
      method: 'POST',
      headers: { 'Idempotency-Key': `order-${order.id}-confirmation` },
      body: JSON.stringify({
        channelId: SALES_CHANNEL,
        type: 'template',
        number: order.phone,
        contactName: order.customerName,
        template: {
          name: 'confirmacao_pedido',
          parameters: [order.customerName, String(order.id)]
        }
      })
    }).then((r) => r.json());
    ```

    The idempotency key uses the order id, so it is always the same for that order. If your routine
    runs twice, the customer gets a single message. To follow the `jobId`, see
    [Sending messages](/en/guides/enviar-mensagens).
  </Step>

  <Step title="Open the card on the board">
    To create the card, provide a name, a board and a stage. The contact is linked in a second call,
    on the linking endpoint:

    ```javascript theme={null}
    const { data: card } = await api('/crm/cards', {
      method: 'POST',
      headers: { 'Idempotency-Key': `order-${order.id}-card` },
      body: JSON.stringify({
        name: `Order #${order.id}`,
        crmBoardId: POST_SALE_BOARD,
        crmBoardListId: AWAITING_SHIPMENT_STAGE,
        description: `${order.items.length} items, total ${order.total}`
      })
    }).then((r) => r.json());

    await api(`/crm/cards/${card.id}/contacts`, {
      method: 'POST',
      body: JSON.stringify({ contactId: contact.id, type: 'add' })
    });
    ```

    The link uses `contact.id`, which is the id returned in step 2. Connecta DESK ids are only valid
    inside Connecta DESK. The order id from your system goes in the card's name or in a custom
    field.
  </Step>
</Steps>

## The patterns that repeat

<AccordionGroup>
  <Accordion title="Upsert instead of create" icon="refresh-cw">
    It is common for an integration to process the same data more than once, because of a retry, a
    reprocessing run or a human error. When an upsert endpoint exists, prefer it. That way the
    routine can run again without creating duplicate records.
  </Accordion>

  <Accordion title="Look up by your system's identifier" icon="key">
    Store your system's identifier in a Connecta DESK field (`code` on the contact, `sku` on the
    product) and search by it. That way the integration can run again without problems, and you do
    not need to maintain a table that maps your ids to Connecta DESK's.
  </Accordion>

  <Accordion title="Idempotency on everything with an external effect" icon="repeat">
    A message sent and a card created are things the end customer sees. On both calls, send an
    `Idempotency-Key` built from a piece of your business data, such as the order id.
  </Accordion>

  <Accordion title="Store what does not change" icon="database">
    Channels, boards, stages, queues, tags and custom fields almost never change. Fetching them on
    every run is the most common way to use up the per-minute limit for no reason.
  </Accordion>
</AccordionGroup>

## Before going to production

<Check>
  The token has only the modules and methods the routine uses, with no `DELETE` "just in case".
</Check>

<Check>
  The key is in an environment variable or a secrets vault, never in the repository.
</Check>

<Check>
  Every call that produces something the customer can see sends an `Idempotency-Key` built from an
  identifier in your system.
</Check>

<Check>
  The integration tells a final error (`4xx`, do not repeat) apart from a temporary one (`429` and
  `5xx`, repeat after waiting). See [Errors](/en/essentials/erros).
</Check>

<Check>
  The log records `error.code`, method and path for every failure. Support uses this information to
  locate the request.
</Check>

<Check>
  If the token has an expiry, the expiration date is written down alongside the integration. An
  expired token gets the same `401` as a wrong token.
</Check>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.