The backup server is a second address running the very same API. It is there for the case when a request to `portal.bulkgate.com` does not reach its destination at all — the backup host accepts the request and forwards it over a different route.

## How the service is built

BulkGate runs on several servers in **three geographical locations**. If one of them goes down, the remaining ones take the traffic over automatically — nothing changes for your integration and `portal.bulkgate.com` keeps answering. That case needs no handling on your side. The whole setup is described in [Infrastructure](https://www.bulkgate.com/en/helpdesk/general-information/api-infrastructure/).

What this does not cover is **the route to us**. An outage on the path between your server and ours — at a carrier, at a connectivity provider or anywhere in between — is outside our control, and the request then never reaches us at all. That is what the backup host is for.

The current service status is at [bulkgate.status.online](https://bulkgate.status.online/en/).

## Address

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

The host is a one-to-one replacement — paths, parameters, credentials and responses are identical to `portal.bulkgate.com`. All you swap is the domain:

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

It works with the same account, the same [token](https://www.bulkgate.com/en/docs/api-administration-tokens/api-tokens/) and the same credit. There is nothing to set up and nothing to switch on.

> [!CAUTION]
> **Do not use the backup host as your primary address.** It can be taken down for maintenance at any time and without notice — it is meant to carry traffic only while the main host is unreachable. Keep `portal.bulkgate.com` first in the order.

## When to switch to it

> [!IMPORTANT]
> The backup host solves a **connection failure, not a rejected request**. To a request the API refused it replies exactly the same way.

Switch when the request **gets no answer**:

| situation | switch? |
|:--------------------------------------------------------------|:-----------|
| Connection or read timeout                                    | **Yes**    |
| DNS could not be resolved                                     | **Yes**    |
| Connection refused, network unreachable                       | **Yes**    |
| HTTP 5xx with no body                                         | **Yes**    |
| A response with an [error type](https://www.bulkgate.com/en/docs/api-administration-tokens/api-error-types/) (4xx) | No       |
| `429 Too Many Requests` ([limits](https://www.bulkgate.com/en/docs/api-administration-tokens/api-request-limits/)) | No        |

In the last two cases the API did answer — the request arrived and was evaluated. Repeating it on the backup host returns the same error and only burns another attempt from your limit.

## Building the failover into your integration

Keep the addresses in an ordered list and try them one after another. If the connection fails, move on to the next one; if the API answered, return the result as it is.

```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' => ['447820125799'],
            'text' => 'Your order 4517 has been shipped.',
            // Guard against sending twice when the first attempt timed out
            // but the message went through anyway — see What to watch out for.
            'duplicates_check' => 'on',
        ], JSON_THROW_ON_ERROR),
    ]);

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

    curl_close($curl);

    // The connection failed — try the next address.
    if ($body === false)
    {
        continue;
    }

    // The API answered, whether with a success or an error. No more switching.
    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': ['447820125799'],
            'text': 'Your order 4517 has been shipped.',
            'duplicates_check': 'on',
        }, timeout=30)
    except requests.RequestException:
        continue   # the connection failed, try the next address

    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: ['447820125799'],
        text: 'Your order 4517 has been shipped.',
        duplicates_check: 'on',
      }),
    });
  } catch {
    continue;   // the connection failed, try the next address
  }

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

## What to watch out for

> [!WARNING]
> A timeout does not mean the message was not sent. The request may have arrived and the response got lost on the way back — a retry on the backup host would then send the message a second time.

That is why you should send `duplicates_check` enabled on the **transactional** endpoints. A message with the same text to the same number is dropped within a **5 minute** window, so the repeated attempt duplicates nothing.

> [!IMPORTANT]
> On **promotional** endpoints `duplicates_check` does not protect a retry — it only removes duplicates within a single batch, not across requests. A repeated request is a new batch to it and goes out in full. For bulk sends, rather check whether the first attempt went through before repeating it.

Set a sensible timeout. Without one the client waits for an answer that never comes and never reaches the backup host at all.

> [!NOTE]
> **API idempotency** is in development — a repeated request will be markable so that the API recognises it and returns the result of the original one instead of sending again. That will remove the difference between transactional and promotional endpoints described above. We will document the procedure here as soon as it is available.
