## Worin sich Sammelberichte unterscheiden

Standardmäßig kommt an Ihre Adresse eine Anfrage pro Nachricht. Bei hohen Volumen
bedeutet das Tausende von Anfragen – die Sammelvariante fasst sie zu Batches zusammen.

| | Einzeln | Gesammelt |
|:--|:--|:--|
| Methode | HTTP GET | HTTP POST |
| Format | Parameter im Query-String | JSON im Body der Anfrage |
| Inhalt | Eine einzelne Nachricht | Ein Array von Datensätzen |

Beide transportieren dasselbe – Zustellberichte ebenso wie eingehende Nachrichten.
Die Einzelvariante ist unter
[Zustellbestätigungen und eingehende SMS](https://www.bulkgate.com/de/dokumentation/api-verwaltung-und-tokens/api-zustellbestaetigungen-und-eingehende-sms/) beschrieben.

## So schalten Sie den Sammelversand ein

Aktivieren Sie in den Webhook-Einstellungen `Massen-DLRs - Massenanfrage`. Die Adresse
und die übrigen Optionen bleiben gleich – es ändert sich nur, wie die Daten bei Ihnen
ankommen.

![Einstellung der Zustellungsberichte mit aktivierter Option für den Sammelversand](docs:api-bulk-delivery-confirmations-and-incoming-sms/bulk-setting_en)

Das gesamte Vorgehen, einschließlich des Orts der Einstellung, ist unter
[Webhooks](https://www.bulkgate.com/de/dokumentation/api-verwaltung-und-tokens/webhooks/) beschrieben.

## So sieht die Anfrage aus

Sie kommt per POST mit dem Header `Content-Type: application/json`. Der Body ist
ein Array, in dem Zustellberichte und eingehende Nachrichten gemischt sind – Sie
unterscheiden sie anhand von `status`.

```http-json
POST /hook/delivery-report HTTP/1.1
Host: example.com
Content-Type: application/json

[
   {
      "status": "1",
      "smsID": "3t2563af3ff285",
      "price": "0.71",
      "date": "1904111007;1904111011"
   },
   {
      "status": "2",
      "smsID": "2t2563af3ff285",
      "price": "0.71",
      "date": "1904111007;1904111011"
   },
   {
      "status": "10",
      "from": "420777777777",
      "message": "Text der Antwort"
   }
]
```

## Parameter eines Datensatzes

| Parameter | Wert | Wann er ankommt |
|:--|:--|:--|
| `status` | Der Statuscode – siehe [Zustellstatus](#zustellstatus) | Immer |
| `smsID` | Die eindeutige ID der Nachricht | Bei einem Zustellbericht |
| `price` | Der Preis der Nachricht in Guthaben | Bei einem Zustellbericht |
| `date` | Zeitpunkt des Versands und der Zustellung, durch ein Semikolon getrennt, Zeitzone MEZ | Bei einem Zustellbericht |
| `from` | Die Telefonnummer des Absenders | Nur bei `status` `10` |
| `message` | Der Text der Nachricht in UTF-8 | Nur bei `status` `10` |

Der Wert `date` hat die Form `YYMMDDHHMM;YYMMDDHHMM` – `1904111007;1904111011`
bedeutet also: gesendet am 11. April 2019 um 10:07 und zugestellt um 10:11.

## Zustellstatus

| Code | Bedeutung |
|:--|:--|
| `1` | Die Nachricht wurde zugestellt |
| `2` | Die Nachricht wartet beim Betreiber – der Empfänger ist momentan nicht erreichbar, die Zustellung wird erneut versucht |
| `3` | Die Nachricht konnte nicht zugestellt werden – der Empfänger ist unbekannt oder dauerhaft nicht erreichbar |
| `10` | Eine eingehende Nachricht bzw. eine Antwort, kein Zustellbericht |
| `13` | Die Nachricht wurde gelesen (nur Viber) |

> [!NOTE]
> Der Status `2` ist nicht endgültig – für dieselbe Nachricht kommt später ein weiterer
> Datensatz mit dem endgültigen Status. Rechnen Sie damit, dass dieselbe `smsID` mehrfach
> auftaucht, unter Umständen sogar im selben Batch.

## Ein Beispiel für die Verarbeitung

Das Skript liest den Body der Anfrage, durchläuft das Array und verarbeitet jeden
Datensatz je nach `status`:

```php
<?php declare(strict_types=1);

const STATUS = [
    1 => 'zugestellt',
    2 => 'wartet beim Betreiber',
    3 => 'nicht zugestellt',
    13 => 'gelesen',
];

function log_line(string $file, string $message): void
{
    file_put_contents(
        __DIR__ . "/$file.log",
        '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL,
        FILE_APPEND
    );
}

function parse_date(string $value): string
{
    // Form YYMMDDHHMM, zum Beispiel 1904111007
    $date = DateTimeImmutable::createFromFormat('ymdHi', $value);

    return $date ? $date->format('Y-m-d H:i') : '?';
}

$body = file_get_contents('php://input');
$records = json_decode($body ?: '', true);

if (!is_array($records))
{
    http_response_code(400);
    exit;
}

foreach ($records as $record)
{
    $status = (int) ($record['status'] ?? 0);

    if ($status === 10) // eingehende Nachricht
    {
        log_line('inbox', sprintf(
            'Von: [%s] Nachricht: [%s]',
            $record['from'] ?? '',
            $record['message'] ?? ''
        ));
    }
    else if (isset(STATUS[$status])) // Zustellbericht
    {
        [$sent, $delivered] = array_pad(explode(';', $record['date'] ?? ''), 2, '');

        log_line('delivery', sprintf(
            'Nachricht [%s]: %s, Preis %.4f, gesendet %s, zugestellt %s',
            $record['smsID'] ?? '',
            STATUS[$status],
            (float) ($record['price'] ?? 0),
            parse_date($sent),
            parse_date($delivered)
        ));
    }
}

http_response_code(200);
```

> [!WARNING]
> Die Adresse muss schnell und mit dem Statuscode `200` antworten. Der gesamte
> Batch wartet auf eine einzige Antwort – lagern Sie aufwendige Verarbeitung daher
> in eine Warteschlange aus und bestätigen Sie die Anfrage sofort.
