--- title: "Callback Events Reference" slug: "webhook-events-reference" updated: 2026-06-23T13:15:19Z published: 2026-06-23T13:15:19Z canonical: "help.frisbii.com/webhook-events-reference" --- > ## Documentation Index > Fetch the complete documentation index at: https://help.frisbii.com/llms.txt > Use this file to discover all available pages before exploring further. # Callback Events Reference Frisbii Media sends callbacks (webhooks) to your registered endpoints in real time when lifecycle events occur — new orders, subscription changes, invoice creation, customer updates, and more. This article covers setup, security, payload structure, all available event types, and retry behaviour. > [!WARNING] > **Data protection note:** Most callback types transmit personal data. The receiving system may qualify as an additional data processor under applicable data protection law. Take this into account when configuring callbacks. --- ## Setup in the Merchant Backend Callbacks are created and managed at client level under **Settings → Development →** [**Callbacks**](/frisbii-media/docs/callback-erstellen-1#create-and-activate-callbacks). ### Create a callback (without authentication) 1. Click **+ Create a Callback** in the action bar 2. Enter the **URL of your web service endpoint** 3. Once a URL is entered, tick the **Enabled** checkbox to activate it 4. Select the **Callback type** from the drop-down menu (e.g. "Create customer") 5. Save — Frisbii Media automatically generates a unique **callback secret** for this callback, visible in the detail view ### Create a callback (with authentication) Tick **Endpoint requires authentication** and choose one or both methods: **Method 1 — Basic Auth (username + password)** The credentials are sent with every request in the standard HTTP `Authorization` header. **Method 2 — Custom HTTP header(s)** Add one or more key-value pairs. These are sent as additional headers with every request. After saving, a **callback secret** is generated. This secret is used to verify the `plenigo-signature` header (see [Verifying the signature](/frisbii-media/docs/webhook-events-reference#verifying-the-plenigosignature) below). ### Manage existing callbacks From the overview, each callback can be: - Opened via **Details** — shows full configuration and the callback secret - Modified via **Edit** — change URL, callback type, or enable/disable - Traced via **Search in Log** — opens the callback log pre-filtered to this callback ID --- ## Request headers Every callback request contains the following headers: | Header | Example value | Description | | --- | --- | --- | | `content-type` | `application/json` | Always JSON | | `plenigo-signature` | `t=1729583536,s=fdcd0a0c…` | HMAC-SHA256 signature for verification (see below) | | `x-plenigo-api-version` | `20240827` | API version used for this callback | | `authorization` | `Basic cGxl…` | Present when Basic Auth is configured | | *(custom headers)* | | Present when custom HTTP headers are configured | --- ## Verifying the plenigo-signature Every callback is signed. Verifying the signature ensures the request originates from Frisbii Media and has not been tampered with. ### Signature format ```plaintext plenigo-signature: t=1729583536,s=fdcd0a0ccd0b4db629d35a33c3aada5cf669a28f91adb38abcc9ffcdb1663d38 ``` | Part | Description | | --- | --- | | `t` | Unix timestamp (seconds since epoch) — when the callback was generated | | `s` | HMAC-SHA256 signature (hex-encoded) | ### How Frisbii Media generates the signature 1. Takes the current Unix timestamp as a string 2. Concatenates: `timestamp + "." + raw request body (JSON string)` 3. Computes HMAC-SHA256 using the **callback secret** as the key 4. Sets the header: `plenigo-signature: t=<timestamp>,s=<signature>` ### How to verify it (step by step) **Step 1 — Parse the header** Split on `,` to get elements. Split each element on `=` to get prefix/value pairs. Extract `t` (timestamp) and `s` (signature). Ignore any other elements. **Step 2 — Build the signed payload string** ```plaintext signed_payload = t + "." + raw_request_body ``` Where `raw_request_body` is the exact JSON string received — do not parse and re-serialize it. **Step 3 — Compute the expected signature** Compute HMAC-SHA256 with: - **Key:** your callback secret (from the Merchant Backend detail view) - **Message:** `signed_payload` from Step 2 **Step 4 — Compare** Compare your computed signature with `s` from the header. Also check that the difference between the current time and `t` is within an acceptable tolerance window (e.g. 5 minutes). > [!CAUTION] > If the timestamp is far in the past, do not process the callback — stale callbacks can cause data inconsistency, especially for customer data. ### PHP example ```php function verifyPlenigoSignature(string $rawBody, string $signatureHeader, string $callbackSecret): bool {    // Parse header: "t=1729583536,s=fdcd0a0c..."    $parts = [];    foreach (explode(',', $signatureHeader) as $part) {        [$key, $value] = explode('=', $part, 2);        $parts[$key] = $value;    }    $timestamp = $parts['t'] ?? null;    $receivedSignature = $parts['s'] ?? null;    if (!$timestamp || !$receivedSignature) {        return false;    }    // Reject callbacks older than 5 minutes    if (abs(time() - (int) $timestamp) > 300) {        return false;    }    // Build signed payload and compute expected signature    $signedPayload = $timestamp . '.' . $rawBody;    $expectedSignature = hash_hmac('sha256', $signedPayload, $callbackSecret);    return hash_equals($expectedSignature, $receivedSignature); } // Usage in your endpoint $rawBody = file_get_contents('php://input'); $signatureHeader = $_SERVER['HTTP_PLENIGO_SIGNATURE'] ?? ''; $callbackSecret = 'YOUR_CALLBACK_SECRET'; // from Merchant Backend detail view if (!verifyPlenigoSignature($rawBody, $signatureHeader, $callbackSecret)) {    http_response_code(401);    exit; } // Signature valid — process the callback $payload = json_decode($rawBody, true); ``` --- ## Payload structure All callbacks share the same outer structure: ```json {  "entityType": "CUSTOMER",  "callbackType": "CREATION",  "entityId": "29",  "entity": {    // content depends on entityType  } } ``` | Field | Description | | --- | --- | | `entityType` | The type of object affected (e.g. `CUSTOMER`, `SUBSCRIPTION`, `INVOICE`) | | `callbackType` | The action that occurred (e.g. `CREATION`, `CHANGE`, `CANCELLATION`) | | `entityId` | The ID of the affected entity — customer ID, subscription ID, invoice ID, etc. | | `entity` | The full entity data object. `null` for `DELETION` events. | --- ## All callback types | Callback type | Entity type | Description | | --- | --- | --- | | `CREATION` | `APP_STORE_ORDER` | An app store order (Apple or Google) was created or updated | | `CHANGE` | `APP_STORE_SUBSCRIPTION` | An app store subscription was activated or deactivated | | `CREATION` | `APP_STORE_SUBSCRIPTION` | An app store subscription was created | | `CHANGE` | `CUSTOMER` | Customer data changed (name, email, password, status, token verification, etc.) | | `CREATION` | `CUSTOMER` | A customer was created or registered | | `DELETION` | `CUSTOMER` | A customer was deleted (`entity` is `null`) | | `CHANGE` | `CUSTOMER_OPT_IN` | Customer opt-in was created or updated | | `PAYMENT_FAILED` | `FAILED_PAYMENT` | A payment attempt failed | | `CHANGE` | `INVOICE` | An invoice changed — cancellation, correction, or status change | | `CREATION` | `INVOICE` | An invoice was created | | `CREATION` | `INVOICE_CANCELLATION` | An invoice was cancelled | | `CREATION` | `INVOICE_CORRECTION` | An invoice was corrected | | `CANCELLATION` | `MULTIUSER_SUBSCRIPTION` | A multi-user subscription was cancelled | | `CREATION` | `MULTIUSER_SUBSCRIPTION` | A multi-user subscription was created | | `UNDO_CANCELLATION` | `MULTIUSER_SUBSCRIPTION` | A multi-user subscription cancellation was reversed | | `CREATION` | `ORDER` | An order was created | | `CONDITIONS_FULFILLED` | `PURCHASED_ADDON` | A purchased add-on's conditions were fulfilled | | `CANCELLATION` | `SUBSCRIPTION` | A subscription was cancelled | | `CHANGE` | `SUBSCRIPTION` | A subscription was changed | | `CREATION` | `SUBSCRIPTION` | A subscription was created | | `ENDED` | `SUBSCRIPTION` | A subscription ended | | `UNDO_CANCELLATION` | `SUBSCRIPTION` | A subscription cancellation was reversed | | `CREATION` | `CALLBACK_RENEWAL` | An API access token was renewed | > [!TIP] > Full JSON schemas for all entity types are available as a download [here](/frisbii-media/docs/payloads-beispiele). --- ## Payload examples ### Create customer ```json {  "entityType": "CUSTOMER",  "callbackType": "CREATION",  "entityId": "29",  "entity": {    "customerId": "29",    "email": "user@example.com",    "firstName": "Test",    "lastName": "",    "username": "Test",    "language": "de",    "salutation": "",    "status": "ACTIVATED",    "registrationSource": "Supporter",    "registrationDate": "2024-08-27T11:49:41.389622Z",    "createdDate": "2024-08-27T11:49:41.389622Z",    "changedDate": "2024-08-27T11:49:41.389622Z",    "twoFactorEnabled": false,    "ageVerificationPinEnabled": false,    "externalSystemId": "",    "miscellaneousData": {}  } } ``` ### Delete customer ```json {  "entityType": "CUSTOMER",  "callbackType": "DELETION",  "entityId": "33",  "entity": null } ``` `entityId` is the customer number. `entity` is `null` because the data has been deleted. ### Create invoice ```json {  "entityType": "INVOICE",  "callbackType": "CREATION",  "entityId": "223",  "entity": {    "invoiceId": 223,    "invoiceDate": "2024-08-28T00:08:51.868510Z",    "status": "NOT_PAID",    "type": "INVOICE",    "currency": "EUR",    "accumulatedPrice": 0.5,    "paymentMethod": "BILLING",    "invoiceCustomerId": "23",    "customerEmail": "user@example.com",    "invoiceAddress": {      "type": "INVOICE",      "salutation": "NONE",      "firstName": "Test",      "lastName": "User",      "city": "Kempten",      "postcode": "87435",      "country": "DE",      "businessAddress": false    },    "items": [      {        "position": 1,        "quantity": 1,        "price": 0.5,        "tax": 7,        "taxCountry": "DE",        "taxType": "DIGITALNEWSPAPER",        "plenigoOfferId": "O_D0SO735QL1AXZH7FTG",        "plenigoProductId": "P_DSQ20PV66PNWGK0LPV",        "periodStartDate": "2024-08-28T00:00:00.000000Z",        "periodEndDate": "2024-08-29T23:59:59.000000Z"      }    ]  } } ``` ### Change subscription ```json {  "entityType": "SUBSCRIPTION",  "callbackType": "CHANGE",  "entityId": "1230116",  "entity": {    "subscriptionId": 1230116,    "status": "ACTIVE",    "plenigoOfferId": "O_628IU8F9CB7I6EH5ZG",    "subscriptionType": "ISSUE_BASED",    "cancellationType": "ISSUE_BASED_REGULAR",    "startDate": "2024-08-02T09:43:13.348191Z",    "cancellationDate": null,    "endDate": null,    "nextBookingDate": null,    "deliveries": 8,    "openDeliveries": 4,    "finishedDeliveries": 4,    "paymentMethod": "CREDIT_CARD",    "paymentMethodDetails": { "brand": "AMERICAN_EXPRESS" },    "invoiceCustomerId": "8",    "deliveryCustomerId": "8",    "currency": "EUR",    "managedBy": "PLENIGO",    "managedExternal": false,    "connectedOffer": false  } } ``` ### Create in-app subscription ```json {  "entityType": "APP_STORE_SUBSCRIPTION",  "callbackType": "CREATION",  "version": "3.0",  "entityId": "1230306",  "entity": {    "appStoreSubscriptionId": 1230206,    "customerId": "29",    "status": "ACTIVE",    "startDate": "2023-12-29T15:01:34.393Z",    "endDate": "2023-12-29T15:06:32.364Z",    "cancellationDate": null,    "accessRightUniqueId": "inappsub",    "externalSystemId": "subscription_1_app_2",    "chainId": 1230206  } } ``` --- ## Responding to callbacks Your endpoint must return **HTTP 200** to acknowledge receipt. Return 200 as quickly as possible — offload processing to a background queue: ```php // 1. Acknowledge immediately http_response_code(200); // 2. Verify signature (see above) $rawBody = file_get_contents('php://input'); $signatureHeader = $_SERVER['HTTP_PLENIGO_SIGNATURE'] ?? ''; if (!verifyPlenigoSignature($rawBody, $signatureHeader, $callbackSecret)) {    exit; // already sent 200 — just don't process } // 3. Queue for async processing $payload = json_decode($rawBody, true); enqueueCallbackJob($payload['entityType'], $payload['callbackType'], $payload['entityId'], $payload['entity']); exit; ``` --- ## Retry behaviour If your endpoint does not return HTTP 200, Frisbii Media retries the callback on the following schedule: | Attempt | Delay after previous attempt | | --- | --- | | 1st retry | 1 hour | | 2nd retry | 6 hours | | 3rd retry | 24 hours | | No further retries | — | **Exception:** If the first attempt returns an error indicating the server does not exist or the callback secret is incorrect, no retries are made. **Auto-deactivation:** If a callback fails 10 or more times within 7 consecutive days, it is automatically deactivated by the system. **Alert email:** If a callback fails 10 or more times within a single day, an automatic system email is sent to the contact email address configured in the client's company data in the Merchant Backend.