# Send paid orders from any PHP backend

> Send MIRA FIVE events and paid orders from any PHP 8.3 backend with mirafive/sdk-php, sent at the end of each request and counted once per webhook.

URL: https://mirafive.io/for/php  
Updated: 2026-09-28

One client sends your server events when the request ends, and an idempotency key makes a repeated webhook count once.

Package `mirafive/sdk-php`

## Set up in 5 steps

1. ### Install the package  
It needs PHP 8.3 or later with `ext-json` and has no required Composer dependencies. It sends with `ext-curl` when it is loaded, otherwise with PHP streams, or through your own PSR-18 client.  
```  
composer require mirafive/sdk-php  
```
2. ### Add the secret key  
Use the secret key of a server source from **Data → Sources**. It stays on the server, so never print it into HTML, JavaScript or a mobile app.  
```  
MIRAFIVE_SECRET_KEY=mf_…  
```
3. ### Create one client  
`new Mira` reads the key from `$_ENV`, `$_SERVER` or `getenv()`. `track()` buffers, and the buffer is sent at the end of the request, every 100 events and on `flush()`. A flush never takes longer than 3 seconds, even when MIRA FIVE is unreachable.  
```  
<?php  
declare(strict_types=1);  
use MiraFive\Mira;  
require __DIR__.'/vendor/autoload.php';  
// Reads MIRAFIVE_SECRET_KEY, and MIRAFIVE_HOST when it is set.  
$mira = new Mira;  
```
4. ### Send the paid order  
Call `track()` with named arguments where the payment is confirmed. Ids are strings, so cast integer ids with `(string)`. The `revenue` property feeds your purchase goal.  
```  
$mira->track('order_paid', userId: (string) $order->customerId, properties: [  
    'revenue' => 328,  
    'currency' => 'EUR',  
]);  
```
5. ### Check the key  
The install check proves the key and host work and is never stored or billed. A wrong key throws a `MiraError` instead. Then track a real event and find it under **Data → Live**.  
```  
$receipt = $mira->send([['name' => '$install_check']]);  
var_dump($receipt->reason); // string(13) "install_check"  
```

MIRA FIVE is analytics built around the person. `mirafive/sdk-php` is its server side for plain PHP and for frameworks without a MIRA FIVE package: it buffers events, sends them when the request ends, and evaluates [feature flags](https://mirafive.io/glossary/feature-flag) in your process. Under PHP-FPM, pass a PSR-16 cache your app already has (`new Mira(cache: $psr16Cache)`), so every PHP process shares the flag document and pauses delivery together during an outage.

## Where do pageviews come from?

From the browser. Add the [two-line script tag](https://mirafive.io/for/website) to your templates with the website key of a website source. Both sources report into the same project, and the secret key never goes into a page.

## How does a paid order reach its channel?

Through the person who paid. With consent, the browser keeps an anonymous id. Send it to your server, for example in a hidden field of the checkout form, and `identify` links the visitor’s earlier visits to your user id:

```php
$mira->identify((string) $user->id, ['plan' => 'pro'], anonymousId: $anonymousId);
```

The paid order carries the same user id, so MIRA FIVE puts it on that person, next to the channel of their first visit. Use your internal id, never an email address. Create a purchase goal for `order_paid` to see buyers and revenue per channel and campaign.

## How do I make a webhook count once?

`send()` delivers at once and returns the server’s `Receipt`. With an idempotency key, a repeat is stored and billed once:

```php
$receipt = $mira->send([
    ['name' => 'order_paid', 'userId' => (string) $order->customerId, 'properties' => ['revenue' => 328, 'currency' => 'EUR']],
], idempotencyKey: "order-{$order->id}");
```

`send()` throws a `MiraError` when MIRA FIVE refuses the batch or cannot be reached after the retries. `track()`and `flush()` never throw for transport reasons.

The source’s Install tab has **Let your coding agent install it**, a prompt for Claude Code, Cursor or Codex with every step and the check; for a server source it never contains the secret key. The [PHP SDK docs](https://docs.mirafive.io/sdks/php) cover queues, transports and flags, and the [setup page](https://mirafive.io/setup) shows who does what, in order.

## Other setups

The same numbers, whichever way your site is built.

- [Astro](https://mirafive.io/for/astro)
- [Next.js](https://mirafive.io/for/nextjs)
- [React](https://mirafive.io/for/react)
- [Laravel](https://mirafive.io/for/laravel)
- [Nuxt](https://mirafive.io/for/nuxt)
- [Symfony](https://mirafive.io/for/symfony)
- [Vue](https://mirafive.io/for/vue)
- [TanStack Start](https://mirafive.io/for/tanstack-start)
- [Node.js](https://mirafive.io/for/nodejs)
- [Convex](https://mirafive.io/for/convex)
- [JavaScript](https://mirafive.io/for/javascript)
- [any website](https://mirafive.io/for/website)

[Read the docs](https://docs.mirafive.io)

## Questions and answers

### What happens when MIRA FIVE is unreachable?

A flush gives up after 3 seconds at most. After a retryable failure, flushes skip the network for 30 seconds and drop their events; with a PSR-16 `cache`, that pause covers every PHP process. `handOff` moves delivery to your queue.

### What about Octane, RoadRunner and queue workers?

Long-running workers serve many requests in one process and reach the end-of-request flush only when they stop. Call `$mira->flush()` after each request or job.

### How do I keep tests and local runs quiet?

`new Mira(enabled: getenv('APP_ENV') === 'production')` sends nothing and needs no key, but still refuses the input production would. To assert on what was sent, pass a `transport` that records requests.

### I use Laravel or Symfony. Is this the right package?

No. `mirafive/sdk-laravel` and `mirafive/sdk-symfony` wire this SDK into the container and send after the response. See the [Laravel setup](https://mirafive.io/for/laravel) and the [Symfony setup](https://mirafive.io/for/symfony).

## See which channel brings buyers

Free for 25,000 events a month. No card needed.

[Start free](https://app.mirafive.io/register)[See pricing](https://mirafive.io/pricing)
