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.
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.
Create a callback (without authentication)
Click + Create a Callback in the action bar
Enter the URL of your web service endpoint
Once a URL is entered, tick the Enabled checkbox to activate it
Select the Callback type from the drop-down menu (e.g. "Create customer")
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 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 |
|---|---|---|
|
| Always JSON |
|
| HMAC-SHA256 signature for verification (see below) |
|
| API version used for this callback |
|
| 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
plenigo-signature: t=1729583536,s=fdcd0a0ccd0b4db629d35a33c3aada5cf669a28f91adb38abcc9ffcdb1663d38
Part | Description |
|---|---|
| Unix timestamp (seconds since epoch) — when the callback was generated |
| HMAC-SHA256 signature (hex-encoded) |
How Frisbii Media generates the signature
Takes the current Unix timestamp as a string
Concatenates:
timestamp + "." + raw request body (JSON string)Computes HMAC-SHA256 using the callback secret as the key
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
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_payloadfrom 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).
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
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:
{
"entityType": "CUSTOMER",
"callbackType": "CREATION",
"entityId": "29",
"entity": {
// content depends on entityType
}
}
Field | Description |
|---|---|
| The type of object affected (e.g. |
| The action that occurred (e.g. |
| The ID of the affected entity — customer ID, subscription ID, invoice ID, etc. |
| The full entity data object. |
All callback types
Callback type | Entity type | Description |
|---|---|---|
|
| An app store order (Apple or Google) was created or updated |
|
| An app store subscription was activated or deactivated |
|
| An app store subscription was created |
|
| Customer data changed (name, email, password, status, token verification, etc.) |
|
| A customer was created or registered |
|
| A customer was deleted ( |
|
| Customer opt-in was created or updated |
|
| A payment attempt failed |
|
| An invoice changed — cancellation, correction, or status change |
|
| An invoice was created |
|
| An invoice was cancelled |
|
| An invoice was corrected |
|
| A multi-user subscription was cancelled |
|
| A multi-user subscription was created |
|
| A multi-user subscription cancellation was reversed |
|
| An order was created |
|
| A purchased add-on's conditions were fulfilled |
|
| A subscription was cancelled |
|
| A subscription was changed |
|
| A subscription was created |
|
| A subscription ended |
|
| A subscription cancellation was reversed |
|
| An API access token was renewed |
Full JSON schemas for all entity types are available as a download here.
Payload examples
Create customer
{
"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
{
"entityType": "CUSTOMER",
"callbackType": "DELETION",
"entityId": "33",
"entity": null
}
entityId is the customer number. entity is null because the data has been deleted.
Create invoice
{
"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
{
"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
{
"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:
// 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.