OperationsStable · 3 min read

Webhooks


Webhooks notify your backend when Perkamo records events and effects. Use them for fulfillment, CRM sync and operational automation that should happen outside the synchronous API request.

Configure endpoints

Webhook endpoints must be public HTTPS URLs.

Create, edit, test, disable or remove endpoints in Webhooks. Each Space can have multiple active endpoints. Perkamo creates one delivery per matching active endpoint.

text

https://api.example.com/webhooks/perkamo

Endpoint event scopes can be * for all events or one event key per line, such as purchase.completed or reward.redeemed. Removing an endpoint disables new deliveries and keeps existing delivery history.

Delivery payload

json

{
"id": "evt_01J...",
"type": "purchase.completed",
"created_at": "2026-06-01T10:00:00.000Z",
"data": {
"space": {
"id": "7c2fc0dd-86af-44f4-a1b0-99b602f99c0f",
"name": "Commerce Live",
"environment": "live"
},
"user_id": "customer_123",
"transaction_id": "order_1092",
"event": "purchase.completed",
"status": "applied",
"occurred_at": "2026-06-01T10:00:00.000Z",
"context": { "amount": 12900, "currency": "CZK" },
"delta": [{ "wallet": "loyalty_points", "amount": 10 }],
"effects": [],
"wallet_state": { "loyalty_points": 120 }
}
}

Headers

Perkamo sends these headers:

HeaderMeaning
user-agentPerkamo-Webhook/1.0
x-perkamo-deliveryUnique delivery id (use as the idempotency key).
x-perkamo-eventEvent type.
x-perkamo-spaceSpace ID.
x-perkamo-attemptAttempt number.
x-perkamo-timestampUnix seconds when the delivery was signed.
x-perkamo-signaturev1=<hex> HMAC-SHA256 signature (see below).

Verify signatures

Every delivery is signed so your handler can confirm it genuinely came from Perkamo and has not been replayed. When you create an endpoint, the API returns a one-time signing_secret (whsec_...) — store it securely; it is never shown again.

The signature is v1= followed by the hex HMAC-SHA256 of ${timestamp}.${body}, keyed by the signing secret, where timestamp is the x-perkamo-timestamp header value and body is the exact raw request body.

Using the server SDK:

ts

import { verifyWebhookSignature } from "@perkamo/sdk";

// `rawBody` must be the exact bytes received (do not re-serialize the JSON).
const ok = await verifyWebhookSignature({
secret: process.env.PERKAMO_WEBHOOK_SECRET!,
payload: rawBody,
signatureHeader: req.headers["x-perkamo-signature"],
timestampHeader: req.headers["x-perkamo-timestamp"],
toleranceSeconds: 300, // reject deliveries older than 5 minutes
});
if (!ok) return res.status(400).end();

Using the PHP SDK:

php

use Perkamo\WebhookSignature;

// $rawBody must be the exact bytes received (do not re-serialize the JSON).
$ok = WebhookSignature::verify(
secret: getenv('PERKAMO_WEBHOOK_SECRET'),
payload: $rawBody,
signatureHeader: $request->headers->get('x-perkamo-signature'),
timestampHeader: $request->headers->get('x-perkamo-timestamp'),
toleranceSeconds: 300, // reject deliveries older than 5 minutes
);
if (!$ok) {
return new Response(status: 400);
}

Verifying manually (any language):

text

expected = "v1=" + hex(hmac_sha256(secret, timestamp + "." + rawBody))

Compare expected to x-perkamo-signature in constant time, and reject requests whose x-perkamo-timestamp is outside your tolerance window. Keep using HTTPS endpoints only and treat the endpoint URL as non-public.

Handler requirements

  • Return any 2xx status for success.
  • Respond within 5 seconds.
  • Store x-perkamo-delivery and make processing idempotent.
  • Do not assume deliveries arrive exactly once.
  • Alert on repeated failures before launching a campaign that depends on fulfillment.

Retries

Delivery status is pending, delivered or failed. Retry scheduling is shown as a delivery property while attempts remain. Delivery history records status code, status text, duration and last error for console debugging.

Use the Webhooks delivery history to inspect failures and resend a delivery after your handler is fixed.