O servidor de reserva é um segundo endereço que executa exatamente a mesma API. Destina-se ao caso em que um pedido para `portal.bulkgate.com` não chega de todo ao destino — o host de reserva aceita o pedido e encaminha-o por uma rota diferente.

## Como o serviço está construído

O BulkGate funciona em vários servidores localizados em **três locais geográficos**. Se um deles falhar, os restantes assumem o tráfego automaticamente — nada muda para a sua integração e `portal.bulkgate.com` continua a responder. Este caso não exige qualquer tratamento da sua parte. Toda a configuração está descrita em [Infrastructure](https://www.bulkgate.com/pt/centro-de-ajuda/informacoes-gerais/infraestrutura/).

O que isto não cobre é **o caminho até nós**. Uma falha no percurso entre o seu servidor e o nosso — num operador, num fornecedor de conectividade ou em qualquer ponto intermédio — está fora do nosso controlo e, nesse caso, o pedido nunca chega até nós. É para isso que serve o host de reserva.

O estado atual do serviço está disponível em [bulkgate.status.online](https://bulkgate.status.online/en/).

## Endereço

```url
https://backup1.bulkgate.com
```

O host é um substituto exato — os caminhos, os parâmetros, os dados de acesso e as respostas são idênticos aos de `portal.bulkgate.com`. Basta trocar o domínio:

```url
https://portal.bulkgate.com/api/2.0/advanced/transactional
https://backup1.bulkgate.com/api/2.0/advanced/transactional
```

Funciona com a mesma conta, o mesmo [token](https://www.bulkgate.com/pt/documentacao/administracao-da-api/tokens-de-api/) e o mesmo crédito. Não há nada para configurar nem para ativar.

> [!CAUTION]
> **Não utilize o host de reserva como endereço principal.** Pode ser desligado para manutenção a qualquer momento e sem aviso prévio — destina-se a transportar tráfego apenas enquanto o host principal estiver inacessível. Mantenha `portal.bulkgate.com` em primeiro lugar na ordem.

## Quando mudar para ele

> [!IMPORTANT]
> O host de reserva resolve uma **falha de ligação, não um pedido rejeitado**. A um pedido que a API recusou, responde exatamente da mesma forma.

Mude quando o pedido **não obtém resposta**:

| situação | mudar? |
|:--------------------------------------------------------------|:-----------|
| Tempo limite de ligação ou de leitura esgotado                | **Sim**    |
| Não foi possível resolver o DNS                               | **Sim**    |
| Ligação recusada, rede inacessível                            | **Sim**    |
| HTTP 5xx sem corpo                                            | **Sim**    |
| Uma resposta com um [tipo de erro](https://www.bulkgate.com/pt/documentacao/administracao-da-api/tipos-de-erro-api/) (4xx) | Não   |
| `429 Too Many Requests` ([limites](https://www.bulkgate.com/pt/documentacao/administracao-da-api/limites-de-pedidos-api/)) | Não    |

Nos dois últimos casos, a API respondeu — o pedido chegou e foi avaliado. Repeti-lo no host de reserva devolve o mesmo erro e apenas consome mais uma tentativa do seu limite.

## Integrar o failover na sua integração

Mantenha os endereços numa lista ordenada e experimente-os um a seguir ao outro. Se a ligação falhar, passe ao seguinte; se a API respondeu, devolva o resultado tal como está.

```php
$hosts = ['portal.bulkgate.com', 'backup1.bulkgate.com'];

foreach ($hosts as $host)
{
    $curl = curl_init();

    curl_setopt_array($curl, [
        CURLOPT_URL => "https://$host/api/2.0/advanced/transactional",
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
        CURLOPT_POSTFIELDS => json_encode([
            'application_id' => 451,
            'application_token' => 'NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj',
            'number' => ['420777777777'],
            'text' => 'Your order 4517 has been shipped.',
            // Proteção contra o envio duplicado quando a primeira tentativa esgotou o tempo limite,
            // mas a mensagem foi enviada na mesma — consulte O que ter em atenção.
            'duplicates_check' => 'on',
        ], JSON_THROW_ON_ERROR),
    ]);

    $body = curl_exec($curl);
    $error = curl_error($curl);

    curl_close($curl);

    // A ligação falhou — experimentar o endereço seguinte.
    if ($body === false)
    {
        continue;
    }

    // A API respondeu, com sucesso ou com erro. Não se muda mais.
    print_r(json_decode($body, true, flags: JSON_THROW_ON_ERROR));

    break;
}
```

```python
# pip install requests
import requests

hosts = ['portal.bulkgate.com', 'backup1.bulkgate.com']

for host in hosts:
    try:
        response = requests.post(f'https://{host}/api/2.0/advanced/transactional', json={
            'application_id': 451,
            'application_token': 'NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj',
            'number': ['420777777777'],
            'text': 'Your order 4517 has been shipped.',
            'duplicates_check': 'on',
        }, timeout=30)
    except requests.RequestException:
        continue   # a ligação falhou, experimentar o endereço seguinte

    print(response.json())
    break
```

```js
const hosts = ['portal.bulkgate.com', 'backup1.bulkgate.com'];

for (const host of hosts) {
  let response;

  try {
    response = await fetch(`https://${host}/api/2.0/advanced/transactional`, {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({
        application_id: 451,
        application_token: 'NKN02TWE6NfBkfl-your-secret-token-Wf05GaqLIWLW4aBj',
        number: ['420777777777'],
        text: 'Your order 4517 has been shipped.',
        duplicates_check: 'on',
      }),
    });
  } catch {
    continue;   // a ligação falhou, experimentar o endereço seguinte
  }

  console.log(await response.json());
  break;
}
```

## O que ter em atenção

> [!WARNING]
> Um tempo limite esgotado não significa que a mensagem não tenha sido enviada. O pedido pode ter chegado e a resposta ter-se perdido no regresso — uma nova tentativa no host de reserva enviaria então a mensagem uma segunda vez.

É por isso que deve enviar `duplicates_check` ativado nos endpoints **transacionais**. Uma mensagem com o mesmo texto para o mesmo número é descartada dentro de uma janela de **5 minutos**, pelo que a tentativa repetida não duplica nada.

> [!IMPORTANT]
> Nos endpoints **promocionais**, `duplicates_check` não protege uma nova tentativa — apenas remove duplicados dentro de um único lote, não entre pedidos. Um pedido repetido é, para ele, um novo lote e é enviado na íntegra. Nos envios em massa, verifique antes se a primeira tentativa foi concluída antes de a repetir.

Defina um tempo limite razoável. Sem ele, o cliente fica à espera de uma resposta que nunca chega e nunca chega a passar para o host de reserva.

> [!NOTE]
> A **idempotência da API** está em desenvolvimento — será possível marcar um pedido repetido para que a API o reconheça e devolva o resultado do pedido original em vez de voltar a enviar. Isso eliminará a diferença entre os endpoints transacionais e promocionais descrita acima. Documentaremos o procedimento aqui assim que estiver disponível.
