> ## Documentation Index
> Fetch the complete documentation index at: https://chariow.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Pulses

> Receive real-time notifications about events in your Chariow store

Pulses are webhook notifications that allow you to receive real-time HTTP POST requests when specific events occur in your Chariow store. Instead of constantly polling the API, Pulses push event data to your configured endpoint URL as soon as something happens, enabling you to build responsive integrations and automations.

## How Pulses Work

<Steps>
  <Step title="Event Occurs">
    An event happens in your store (e.g., a sale is completed).
  </Step>

  <Step title="Pulse Triggered">
    Chariow sends an HTTP POST request to your configured endpoint.
  </Step>

  <Step title="Process the Event">
    Your server receives the payload and processes it.
  </Step>

  <Step title="Acknowledge Receipt">
    Your server returns a 2xx status code to confirm receipt.
  </Step>
</Steps>

## Setting Up Pulses

You can configure Pulses in several ways:

### Via Store Dashboard

1. Go to **Automation** → **Pulses**
2. Click **Add Pulse**
3. Enter your webhook endpoint URL (must be HTTPS)
4. Select the events you want to receive
5. Optionally select specific products (leave empty for all products)
6. Save your Pulse

<Note>
  Your Pulse endpoint must be accessible via HTTPS. HTTP endpoints are not supported for security reasons.
</Note>

## Pulse Events

Pulses support the following events. When an event fires, the webhook payload contains an `event` field with the event value.

### Sale Events

| Event Value       | Label           | Description                       |
| ----------------- | --------------- | --------------------------------- |
| `successful.sale` | Successful Sale | Triggers when a sale is completed |
| `abandoned.sale`  | Abandoned Sale  | Triggers when a sale is abandoned |
| `failed.sale`     | Failed Sale     | Triggers when a sale fails        |

### License Events

| Event Value         | Label             | Description                                     |
| ------------------- | ----------------- | ----------------------------------------------- |
| `license.activated` | License Activated | Triggers when a license is activated            |
| `license.expired`   | License Expired   | Triggers when a license expires                 |
| `license.issued`    | License Issued    | Triggers when a license is issued to a customer |
| `license.revoked`   | License Revoked   | Triggers when a license is revoked              |

### Affiliate Events

| Event Value        | Label            | Description                                    |
| ------------------ | ---------------- | ---------------------------------------------- |
| `affiliate.joined` | Affiliate Joined | Triggers when a new affiliate joins your store |

<Tip>
  You can configure multiple events for a single Pulse URL. When any of the selected events occur, Chariow will send a webhook notification to your endpoint with the corresponding event value.
</Tip>

## Pulse Payload

When a configured event occurs, Chariow sends an HTTP POST request to your webhook URL with event data in the request body. The exact payload structure depends on the event type.

<Note>
  Pulse payloads contain comprehensive event data, including details about the entity (sale, license, etc.), customer information, product details, and store context. The payload structure varies by event type to provide relevant information for each event.
</Note>

### Successful Sale Example

When a successful sale occurs, Chariow sends a webhook with the `successful.sale` event:

```json theme={null}
{
  "event": "successful.sale",
  "sale": {
    "id": "sal_xyz789abc",
    "amount": {
      "value": 99,
      "formatted": "$99.00",
      "short": "99",
      "currency": "USD"
    },
    "original_amount": {
      "value": 99,
      "formatted": "$99.00",
      "short": "99",
      "currency": "USD"
    },
    "discount_amount": {
      "value": 0,
      "formatted": "$0.00",
      "short": "0",
      "currency": "USD"
    },
    "settlement": {
      "amount": {
        "value": 89.10,
        "formatted": "$89.10",
        "short": "89",
        "currency": "USD"
      },
      "due_at": "2025-01-22T10:30:00+00:00",
      "done_at": null,
      "service_fee": {
        "value": 4.95,
        "formatted": "$4.95",
        "short": "5",
        "currency": "USD"
      },
      "payment_fee": {
        "value": 4.95,
        "formatted": "$4.95",
        "short": "5",
        "currency": "USD"
      },
      "fee": {
        "value": 9.90,
        "formatted": "$9.90",
        "short": "10",
        "currency": "USD"
      }
    },
    "status": "completed",
    "created_at": "2025-01-15T10:30:00+00:00",
    "custom_fields": null,
    "custom_metadata": {"order_ref": "ORD-123"},
    "completed_at": "2025-01-15T10:32:00+00:00",
    "abandoned_at": null,
    "failed_at": null
  },
  "product": {
    "id": "prd_def456",
    "name": "Premium Course",
    "url": "https://mystore.mychariow.com/p/premium-course",
    "price": {
      "value": 99,
      "formatted": "$99.00",
      "short": "99",
      "currency": "USD"
    }
  },
  "customer": {
    "id": "cus_abc123",
    "name": "John Doe",
    "first_name": "John",
    "last_name": "Doe",
    "email": "customer@example.com",
    "phone": "+1234567890",
    "country": "US"
  },
  "affiliate": null,
  "store": {
    "id": "str_xyz789",
    "name": "My Digital Store",
    "url": "https://mystore.mychariow.com"
  },
  "checkout": {
    "url": "https://mystore.mychariow.com/checkout/sal_xyz789abc"
  }
}
```

### License Activated Example

When a license is activated, Chariow sends a webhook with the `license.activated` event:

```json theme={null}
{
  "event": "license.activated",
  "license": {
    "id": "lic_ghi789def",
    "key": "ABCD-1234-EFGH-5678",
    "status": "active",
    "source_type": "sale",
    "activation_count": 1,
    "max_activations": 5,
    "activated_at": "2025-01-15T10:35:00+00:00",
    "expires_at": "2026-01-15T10:35:00+00:00",
    "expired_at": null,
    "revoked_at": null,
    "created_at": "2025-01-15T10:30:00+00:00"
  },
  "product": {
    "id": "prd_def456",
    "name": "Pro Software License",
    "url": "https://mystore.mychariow.com/p/pro-software"
  },
  "customer": {
    "id": "cus_abc123",
    "name": "John Doe",
    "first_name": "John",
    "last_name": "Doe",
    "email": "customer@example.com",
    "phone": "+1234567890",
    "country": "US"
  },
  "store": {
    "id": "str_xyz789",
    "name": "My Digital Store",
    "url": "https://mystore.mychariow.com"
  }
}
```

### Affiliate Joined Example

When a new affiliate joins your store, Chariow sends a webhook with the `affiliate.joined` event:

```json theme={null}
{
  "event": "affiliate.joined",
  "affiliate": {
    "id": "saff_xyz789abc",
    "account": {
      "id": "aff_abc123def",
      "code": "CREATOR123",
      "pseudo": "creative_studio",
      "email": "creator@example.com",
      "first_name": "John",
      "last_name": "Doe",
      "name": "John Doe",
      "country": {
        "code": "US",
        "name": "United States"
      },
      "phone": {
        "country_code": "US",
        "formatted": "+1 234 567 890"
      }
    },
    "source": "invitation",
    "status": "active",
    "joined_at": "2025-01-15T10:40:00+00:00"
  },
  "store": {
    "id": "str_xyz789",
    "name": "My Digital Store",
    "url": "https://mystore.mychariow.com"
  }
}
```

<Warning>
  The exact payload structure may include additional fields depending on the event type and context. Always check the actual payload received at your endpoint for the complete data structure.
</Warning>

## Handling Pulses

### Basic Example (Node.js/Express)

```javascript theme={null}
const express = require('express');
const app = express();

app.post('/webhooks/chariow', express.json(), (req, res) => {
  const payload = req.body;
  const event = payload.event;

  switch (event) {
    case 'successful.sale':
      handleSuccessfulSale(payload.sale, payload.product, payload.customer, payload.store);
      break;
    case 'abandoned.sale':
      handleAbandonedSale(payload.sale, payload.store);
      break;
    case 'failed.sale':
      handleFailedSale(payload.sale, payload.store);
      break;
    case 'license.activated':
      handleLicenseActivated(payload.license, payload.product, payload.customer);
      break;
    case 'license.expired':
      handleLicenseExpired(payload.license, payload.customer);
      break;
    case 'license.issued':
      handleLicenseIssued(payload.license, payload.product, payload.customer);
      break;
    case 'license.revoked':
      handleLicenseRevoked(payload.license, payload.customer);
      break;
    case 'affiliate.joined':
      handleAffiliateJoined(payload.affiliate, payload.store);
      break;
    default:
      console.log(`Unhandled event type: ${event}`);
  }

  // Always return 200 to acknowledge receipt
  res.status(200).send('OK');
});

function handleSuccessfulSale(sale, store) {
  console.log(`Sale completed: ${sale.id} in store ${store.name}`);
  // Add customer to CRM, send confirmation email, grant access, etc.
}

function handleLicenseActivated(license, customer) {
  console.log(`License activated: ${license.key} for ${customer.email}`);
  // Log activation, update internal records, notify customer, etc.
}

function handleAffiliateJoined(affiliate, store) {
  console.log(`New affiliate joined: ${affiliate.account.code} in store ${store.name}`);
  // Send welcome email, create affiliate dashboard access, notify team, etc.
}

app.listen(3000, () => console.log('Webhook server running on port 3000'));
```

### PHP Example

```php theme={null}
<?php

// Read the raw POST data
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);

$event = $data['event'] ?? null;

switch ($event) {
    case 'successful.sale':
        handleSuccessfulSale($data['sale'], $data['product'], $data['customer'], $data['store']);
        break;
    case 'abandoned.sale':
        handleAbandonedSale($data['sale'], $data['store']);
        break;
    case 'failed.sale':
        handleFailedSale($data['sale'], $data['store']);
        break;
    case 'license.activated':
        handleLicenseActivated($data['license'], $data['product'], $data['customer']);
        break;
    case 'license.expired':
        handleLicenseExpired($data['license'], $data['customer']);
        break;
    case 'license.issued':
        handleLicenseIssued($data['license'], $data['product'], $data['customer']);
        break;
    case 'license.revoked':
        handleLicenseRevoked($data['license'], $data['customer']);
        break;
    case 'affiliate.joined':
        handleAffiliateJoined($data['affiliate'], $data['store']);
        break;
    default:
        error_log("Unhandled event type: " . $event);
}

// Always return 200 to acknowledge receipt
http_response_code(200);
echo 'OK';

function handleSuccessfulSale($sale, $store) {
    error_log("Sale completed: {$sale['id']} in store {$store['name']}");
    // Add customer to CRM, send confirmation email, grant access, etc.
}

function handleLicenseActivated($license, $customer) {
    error_log("License activated: {$license['key']} for {$customer['email']}");
    // Log activation, update internal records, notify customer, etc.
}

function handleAffiliateJoined($affiliate, $store) {
    error_log("New affiliate joined: {$affiliate['account']['code']} in store {$store['name']}");
    // Send welcome email, create affiliate dashboard access, notify team, etc.
}
```

## Retry Policy

If your endpoint doesn't respond with a 2xx status code, Chariow will retry the Pulse:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 1 minute   |
| 2nd retry | 5 minutes  |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours    |
| 5th retry | 24 hours   |

After 5 failed attempts, the Pulse is marked as failed.

<Warning>
  Ensure your Pulse endpoint responds quickly (within 30 seconds). Long-running processes should be handled asynchronously.
</Warning>

## Best Practices

<AccordionGroup>
  <Accordion title="Respond Quickly" icon="bolt">
    Return a 200 response immediately, then process the Pulse asynchronously to avoid timeouts:

    ```javascript theme={null}
    app.post('/webhooks/chariow', (req, res) => {
      // Acknowledge receipt immediately
      res.status(200).send('OK');

      // Process asynchronously (e.g., queue job, background worker)
      queuePulseProcessing(req.body);
    });
    ```
  </Accordion>

  <Accordion title="Handle Duplicates" icon="clone">
    Due to retry logic, Pulses may be sent multiple times. Implement idempotency by tracking processed events:

    ```javascript theme={null}
    const processedEvents = new Set();

    async function processPulse(payload) {
      // Create unique identifier from pulse data
      const uniqueId = `${payload.event}_${payload.sale?.id || payload.license?.id || payload.affiliate?.id}`;

      if (processedEvents.has(uniqueId)) {
        console.log('Duplicate pulse, skipping');
        return;
      }

      processedEvents.add(uniqueId);
      // Process the pulse...
    }
    ```
  </Accordion>

  <Accordion title="Use HTTPS" icon="lock">
    Always use HTTPS for your Pulse endpoint to ensure data is encrypted in transit. Chariow will reject HTTP endpoints for security reasons.
  </Accordion>

  <Accordion title="Monitor Failures" icon="chart-line">
    Monitor your Pulse endpoint for failures and errors. Check your store dashboard regularly for failed Pulse deliveries and investigate the root cause.
  </Accordion>

  <Accordion title="Filter by Products" icon="filter">
    For high-volume stores, consider creating separate Pulses for different products to make processing more efficient and organised.
  </Accordion>
</AccordionGroup>

## Testing Pulses

Use the Pulse testing feature in your dashboard:

1. Go to **Automation** → **Pulses**
2. Click on your Pulse
3. Click **Send Test Event**
4. Select an event type
5. Check your endpoint received the test payload

<Info>
  For local development, use a service like [ngrok](https://ngrok.com) to expose your local server to the internet.
</Info>

## Managing Pulses via API

You can programmatically manage your Pulses using the Chariow Public API:

### List All Pulses

```bash theme={null}
curl -X GET "https://api.chariow.com/v1/pulses" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

#### Example List Response

```json theme={null}
{
  "data": [
    {
      "id": "pls_abc123xyz",
      "url": "https://example.com/webhooks/chariow",
      "is_enabled": true,
      "source": {
        "value": "manual",
        "label": "Manual",
        "description": "Created manually"
      },
      "triggers": [
        {
          "value": "successful_sale",
          "label": "Successful Sale",
          "description": "Triggers when a sale is completed"
        }
      ],
      "products": [
        {
          "id": "prd_def456",
          "name": "Premium Course",
          "type": "course",
          "pictures": {
            "thumbnail": "https://cdn.chariow.com/products/thumb.jpg",
            "cover": "https://cdn.chariow.com/products/cover.jpg"
          },
          "category": {
            "value": "education_and_learning",
            "label": "Education and Learning"
          },
          "pricing": {
            "type": "one_time",
            "price": {
              "value": 99,
              "formatted": "$99.00",
              "short": "99",
              "currency": "USD"
            },
            "effective": {
              "value": 99,
              "formatted": "$99.00",
              "short": "99",
              "currency": "USD"
            }
          },
          "bundle": null,
          "metadata": null
        }
      ],
      "store": {
        "id": "str_xyz789",
        "name": "My Digital Store",
        "logo_url": "https://cdn.chariow.com/stores/xyz789/logo.png",
        "url": "https://mystore.mychariow.com"
      },
      "is_editable": true,
      "created_at": "2025-01-10T08:00:00+00:00",
      "updated_at": "2025-01-10T08:00:00+00:00"
    }
  ],
  "pagination": {
    "next_cursor": null,
    "prev_cursor": null,
    "has_more": false
  }
}
```

### Get a Specific Pulse

```bash theme={null}
curl -X GET "https://api.chariow.com/v1/pulses/pls_abc123xyz" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

The response for a single pulse uses the same structure as each item in the list response.

### Filter Pulses

You can filter pulses by URL or event type using the search parameter:

```bash theme={null}
curl -X GET "https://api.chariow.com/v1/pulses?search=successful_sale" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

<Info>
  For complete API documentation, see the [List Pulses](/api-reference/pulses/list-pulses) and [Get Pulse](/api-reference/pulses/get-pulse) endpoints.
</Info>

## Related Resources

<CardGroup cols={2}>
  <Card title="API Reference - List Pulses" icon="webhook" href="/api-reference/pulses/list-pulses">
    View detailed API documentation
  </Card>

  <Card title="API Reference - Get Pulse" icon="webhook" href="/api-reference/pulses/get-pulse">
    Get a specific pulse via API
  </Card>

  <Card title="Sales Guide" icon="receipt" href="/en/guides/sales">
    Learn about sale events
  </Card>

  <Card title="Licenses Guide" icon="key" href="/en/guides/licenses">
    Learn about license events
  </Card>

  <Card title="Affiliates Guide" icon="users" href="/en/guides/affiliates">
    Learn about affiliate events
  </Card>
</CardGroup>
