# Validate purchases from your backend

# Validate purchases from your backend

Tracking on the page or in your app tells Frak an order might be coming. Rewards only fire once your backend confirms the order is real with a signed webhook. This keeps rewards tied to genuine, paid sales.

This step is the same whether your front end is a [website](/guides/platforms/custom/web/) or a [mobile app](/guides/platforms/custom/mobile/).

## How it works

1. **The front end registers the order.** The post-purchase card, `trackPurchaseStatus`, or the mobile SDK's purchase call sends `customerId`, `orderId`, and `token` so Frak starts listening for that order.

2. **Your backend confirms it.** When the order is paid (or refunded, cancelled), your server sends a webhook to Frak with the same identifiers and an HMAC signature.

3. **Frak triggers the reward.** Once the signature checks out and the status is `confirmed`, Frak sends the `PurchaseCompleted` interaction, which can pay out rewards based on your active campaigns.

## Send the webhook

Your webhook URL and signing secret are in the [business dashboard](https://business.frak.id/), under the **Purchase Tracker** section of your merchant. Sign the entire request body with HMAC SHA-256 and send it in the `x-hmac-sha256` header.
```ts
import crypto from "node:crypto";

async function sendPurchaseWebhook(order: {
  id: string;
  customerId: string;
  status: "pending" | "confirmed" | "cancelled" | "refunded";
  token: string;
  currency?: string;
  totalPrice?: string;
}) {
  const body = JSON.stringify(order);
  const hmac = crypto
    .createHmac("sha256", process.env.FRAK_WEBHOOK_SECRET)
    .update(body)
    .digest("hex");

  await fetch(process.env.FRAK_WEBHOOK_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-hmac-sha256": hmac,
      // Use "true" while testing, "false" in production
      "x-test": "false",
    },
    body,
  });
}

await sendPurchaseWebhook({
  id: "order_456",
  customerId: "cust_123",
  status: "confirmed",
  token: "a-unique-order-token",
  currency: "EUR",
  totalPrice: "99.99",
});
```

```php
function sendPurchaseWebhook(array $order): void {
    $url = getenv('FRAK_WEBHOOK_URL');
    $secret = getenv('FRAK_WEBHOOK_SECRET');

    $body = json_encode($order);
    $hmac = hash_hmac('sha256', $body, $secret);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $body,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'x-hmac-sha256: ' . $hmac,
            'x-test: false', // "true" while testing
        ],
    ]);
    curl_exec($ch);
    curl_close($ch);
}

sendPurchaseWebhook([
    'id' => 'order_456',
    'customerId' => 'cust_123',
    'status' => 'confirmed',
    'token' => 'a-unique-order-token',
    'currency' => 'EUR',
    'totalPrice' => '99.99',
]);
```

**The identifiers must match:** The webhook's `id` is the same value as the order ID your front end sent, and `customerId` and `token` must match the values you sent when tracking. If they differ, Frak cannot link the confirmation to the order and no reward fires.

See the [Purchase webhook reference](/developers/api/webhook/) for the full payload (including line items) and the [track purchase endpoint](/developers/api/track-purchase/) for the page-side call.

## Next steps

[Add funds](/guides/dashboard/configure/funds/)
  [Create a campaign](/guides/campaigns/create/)
  [Purchase webhook reference](/developers/api/webhook/)