Le serveur de secours est une seconde adresse qui exécute exactement la même API. Il sert dans le cas où une requête vers `portal.bulkgate.com` n'atteint pas du tout sa destination — l'hôte de secours accepte la requête et la transmet par un autre chemin.

## Comment le service est construit

BulkGate fonctionne sur plusieurs serveurs répartis sur **trois sites géographiques**. Si l'un d'eux tombe en panne, les autres reprennent automatiquement le trafic — rien ne change pour votre intégration et `portal.bulkgate.com` continue de répondre. Ce cas ne demande aucune gestion de votre côté. L'ensemble de l'architecture est décrit dans [Infrastructure](https://www.bulkgate.com/fr/centre-d-aide/informations-generales/infrastructure/).

Ce que cela ne couvre pas, c'est **le chemin jusqu'à nous**. Une panne sur le trajet entre votre serveur et le nôtre — chez un opérateur, chez un fournisseur de connectivité ou n'importe où entre les deux — échappe à notre contrôle, et la requête ne nous parvient alors jamais. C'est à cela que sert l'hôte de secours.

L'état actuel du service est disponible sur [bulkgate.status.online](https://bulkgate.status.online/en/).

## Adresse

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

L'hôte est un remplacement à l'identique — les chemins, les paramètres, les identifiants et les réponses sont les mêmes que sur `portal.bulkgate.com`. Vous ne changez que le domaine :

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

Il fonctionne avec le même compte, le même [token](https://www.bulkgate.com/fr/documentation/administration-api-et-tokens/tokens-d-api/) et le même crédit. Il n'y a rien à configurer ni rien à activer.

> [!CAUTION]
> **N'utilisez pas l'hôte de secours comme adresse principale.** Il peut être mis hors service pour maintenance à tout moment et sans préavis — il est destiné à acheminer le trafic uniquement lorsque l'hôte principal est injoignable. Gardez `portal.bulkgate.com` en première position.

## Quand basculer

> [!IMPORTANT]
> L'hôte de secours résout une **panne de connexion, pas une requête rejetée**. À une requête que l'API a refusée, il répond exactement de la même manière.

Basculez lorsque la requête **ne reçoit aucune réponse** :

| situation | basculer ? |
|:--------------------------------------------------------------|:-----------|
| Délai de connexion ou de lecture dépassé                      | **Oui**    |
| Le DNS n'a pas pu être résolu                                 | **Oui**    |
| Connexion refusée, réseau injoignable                         | **Oui**    |
| HTTP 5xx sans corps                                           | **Oui**    |
| Une réponse avec un [type d'erreur](https://www.bulkgate.com/fr/documentation/administration-api-et-tokens/types-d-erreurs-api/) (4xx) | Non  |
| `429 Too Many Requests` ([limites](https://www.bulkgate.com/fr/documentation/administration-api-et-tokens/limites-requetes-api/)) | Non       |

Dans les deux derniers cas, l'API a bien répondu — la requête est arrivée et a été évaluée. La répéter sur l'hôte de secours renvoie la même erreur et ne fait que consommer une tentative supplémentaire de votre limite.

## Intégrer la bascule dans votre intégration

Conservez les adresses dans une liste ordonnée et essayez-les l'une après l'autre. Si la connexion échoue, passez à la suivante ; si l'API a répondu, renvoyez le résultat tel quel.

```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' => 'Votre commande 4517 a été expédiée.',
            // Protection contre un double envoi lorsque la première tentative a expiré,
            // mais que le message est quand même parti — voir Points d'attention.
            'duplicates_check' => 'on',
        ], JSON_THROW_ON_ERROR),
    ]);

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

    curl_close($curl);

    // La connexion a échoué — essayer l'adresse suivante.
    if ($body === false)
    {
        continue;
    }

    // L'API a répondu, que ce soit par un succès ou par une erreur. Plus de bascule.
    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': 'Votre commande 4517 a été expédiée.',
            'duplicates_check': 'on',
        }, timeout=30)
    except requests.RequestException:
        continue   # la connexion a échoué, essayer l'adresse suivante

    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: 'Votre commande 4517 a été expédiée.',
        duplicates_check: 'on',
      }),
    });
  } catch {
    continue;   // la connexion a échoué, essayer l'adresse suivante
  }

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

## Points d'attention

> [!WARNING]
> Un délai dépassé ne signifie pas que le message n'a pas été envoyé. La requête a pu arriver et la réponse se perdre au retour — une nouvelle tentative sur l'hôte de secours enverrait alors le message une seconde fois.

C'est pourquoi vous devez envoyer `duplicates_check` activé sur les endpoints **transactionnels**. Un message avec le même texte vers le même numéro est écarté dans une fenêtre de **5 minutes**, de sorte que la tentative répétée ne crée aucun doublon.

> [!IMPORTANT]
> Sur les endpoints **promotionnels**, `duplicates_check` ne protège pas une nouvelle tentative — il supprime uniquement les doublons au sein d'un même lot, pas entre plusieurs requêtes. Une requête répétée constitue pour lui un nouveau lot et part en entier. Pour les envois en masse, vérifiez plutôt si la première tentative a abouti avant de la répéter.

Définissez un délai d'attente raisonnable. Sans lui, le client attend une réponse qui ne vient jamais et n'atteint jamais l'hôte de secours.

> [!NOTE]
> **L'idempotence de l'API** est en cours de développement — il sera possible de marquer une requête répétée afin que l'API la reconnaisse et renvoie le résultat de la requête d'origine au lieu d'envoyer de nouveau. Cela supprimera la différence entre endpoints transactionnels et promotionnels décrite ci-dessus. Nous documenterons la procédure ici dès qu'elle sera disponible.
