# Purchase Webhooks

# Purchase Webhooks

## Summary
The purchase webhooks allow your application to validate purchase events and automatically trigger the `PurchaseCompleted` interaction, which could potentially lead to user rewards based on active campaigns. The webhooks also update the on-chain purchase oracle for your merchant, allowing you to know whether a purchase has been finalized. All purchase data is anonymized, and the oracle is updated with a Merkle tree root where each leaf contains a hashed `purchaseId`, `userId`, and the purchase finality status.

## Available Webhooks
We provide three types of webhooks to handle purchase tracking:

1. **Shopify Webhook**
2. **WooCommerce Webhook**
3. **Custom Webhook**

### Shopify Webhook
The Shopify webhook should be set up to trigger on order updates, allowing the backend to validate purchases and update the oracle. It is provisioned automatically by the official Frak Shopify app during onboarding — see the [Shopify Integration Guide](/guides/shopify) for details.

### WooCommerce Webhook
Similar to the Shopify webhook, the WooCommerce webhook is provisioned automatically by the Frak WordPress plugin once the webhook secret is pasted in. Follow the [WordPress integration guide](/guides/platforms/wordpress/details/#order-tracking-with-woocommerce) to properly set it up.

### Custom Webhook
The Custom webhook is designed for applications that use a custom eCommerce solution or need more flexibility. This webhook requires specific headers and a payload to register the purchase event.

#### Endpoint
The specific endpoint URL for each webhook type is visible in the business dashboard under the `Purchase Tracker` section of your merchant.

#### Headers
- **`x-hmac-sha256`**: A HMAC SHA-256 digest of the entire request body. The HMAC is computed using the secret available in your business dashboard.
- **`x-test`**: Indicates if the webhook call is for a test order. This is useful for development environments.

#### Request Body
The body of the Custom webhook should be a JSON object that follows the `CustomWebhookDto` type:

```ts
type CustomWebhookDto = Readonly<{
  /**
   * Id of the order on your side (external order id)
   */
  id: string;

  /**
   * Id of the customer on your side (external customer id)
   */
  customerId: string;

  /**
   * Status of the order
   */
  status: "pending" | "confirmed" | "cancelled" | "refunded";

  /**
   * Custom token for this order
   *  - Should be the same one as the one exposed to the end user and submitted through the `listenForPurchase` API method
   */
  token: string;

  /**
   * Currency code (ISO 4217)
   *  - Optional, could be empty
   *  - Recommended for the UX in the members space
   */
  currency?: string;

  /**
   * The total price of the order, in the currency provided
   *  - Optional, could be empty
   *  - Recommended for the UX in the members space
   */
  totalPrice?: string;

  /**
   * All the items in the order
   *  - Optional
   *  - Will be used in the wallet members space, to display additional data to the end user, e.g. which product they referee bought the most
   */
  items?: {
    /**
     * The product id on your side
     */
    productId: string;

    /**
     * The quantity of the product in the order
     */
    quantity: number;

    /**
     * The price of the product
     */
    price: string;

    /**
     * An internal name for the product, used in url slug
     */
    name: string;

    /**
     * The displayable title of the product
     */
    title: string;

    /**
     * A potential image URL for the product
     */
    image?: string;
  }[];
}>;
```

### Example

Here is a few custom webhook calls in different languages:

```ts
import crypto from 'crypto';

type Purchase = {
  id: string;
  customerId: string;
  status: "pending" | "confirmed" | "cancelled" | "refunded";
  token: string;
  currency?: string;
  totalPrice?: string;
  items?: {
    productId: string;
    quantity: number;
    price: string;
    name: string;
    title: string;
    image?: string;
  }[];
}

async function sendPurchaseWebhook(
  purchase: Purchase
): Promise<void> {
  const body = JSON.stringify(purchase);
  const hmac = await crypto
    .createHmac('sha256', process.env.FRAK_PURCHASE_WEBHOOK_SECRET)
    .update(body)
    .digest('hex');

  await fetch(process.env.FRAK_PURCHASE_WEBHOOK, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-hmac-sha256': hmac,
      'x-test': process.env.NODE_ENV === 'production' ? 'false' : 'true'
    },
    body
  });
}

// Usage example
const purchase = {
    id: 'order_123',
    customerId: 'cust_456',
    status: 'confirmed',
    token: 'purchase_token_789',
    currency: 'USD',
    totalPrice: '99.99',
    items: [{
      productId: 'prod_001',
      quantity: 1,
      price: '99.99',
      name: 'premium-subscription',
      title: 'Premium Subscription'
    }]
  };

sendPurchaseWebhook(purchase)
  .then(response => console.log('Webhook sent successfully:', response))
  .catch(error => console.error('Error sending webhook:', error));
```

```php
function sendPurchaseWebhook(array $purchase): void {
    $webhookUrl = 'your_webhook_url_from_dashboard';
    $secretKey = 'your_secret_key_from_dashboard';
    $isTest = true; // Set to false in production

    $body = json_encode($purchase);
    $hmac = hash_hmac('sha256', $body, $secretKey);

    $ch = curl_init($webhookUrl);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $body,
        CURLOPT_RETURNTRANSFER => false,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'x-hmac-sha256: ' . $hmac,
            'x-test: ' . ($isTest ? 'true' : 'false')
        ]
    ]);

    curl_exec($ch);
    curl_close($ch);
}

// Usage example
$purchase = [
    'id' => 'order_123',
    'customerId' => 'cust_456',
    'status' => 'confirmed',
    'token' => 'purchase_token_789',
    'currency' => 'USD',
    'totalPrice' => '99.99',
    'items' => [
        [
            'productId' => 'prod_001',
            'quantity' => 1,
            'price' => '99.99',
            'name' => 'premium-subscription',
            'title' => 'Premium Subscription'
        ]
    ]
];

sendPurchaseWebhook($purchase);
```

## HMAC Signature
Each webhook request should include a valid HMAC signature of the entire body payload, ensuring the request's integrity and authenticity. The secret used to generate this HMAC signature is available in your business dashboard.

## Response
All webhook types expect a JSON response indicating whether the request was successfully processed.

## Notes
- Ensure that the `id`, `customerId`, `status`, and `token` fields are consistent between the client and the server to ensure successful tracking.
- The webhooks automatically update the on-chain purchase oracle, which anonymously tracks the purchase finality status.
- You can find the specific webhook URL for your merchant in the business dashboard, under the `Purchase Tracker` section of your merchant.