---
title: "PHP store fulfillment reference"
description: "Run the complete PHP trusted-command receiver and durable licensing worker with private state, encrypted key export and Sandbox evidence."
slug: "/docs/v1/guides/any-store/php"
section: "Webhooks and integrations"
order: 783
audience: "all"
level: "advanced"
last_updated: "2026-10-18"
version: "v1"
related: ["/docs/v1/guides/any-store","/docs/v1/api/idempotency"]
draft: false
---
Run the complete PHP reference after your trusted store adapter has independently verified the purchase and authorized one durable licensing effect.

## What you will learn

- Install the exact reference dependencies without changing KDPika's application.
- Start a loopback receiver and run its bounded worker separately.
- Inspect durable outcomes and export a saved encrypted key for private [delivery](/docs/v1/start/glossary#delivery).

## Prerequisites

- Read the complete [store backend protocol and mapping guide](/docs/v1/guides/any-store).
- PHP 8.2+ with cURL, PDO SQLite, mbstring and OpenSSL supporting AES-256-GCM. The HTTP handler and CLI cron must use compatible PHP extensions and private state.
- Use a POSIX macOS/Linux-oriented private filesystem, not a public download or temporary state directory.
- Configure the private mapping and secret environment variables from the parent guide.
- Use a [Sandbox](/docs/v1/start/glossary#sandbox) [product](/docs/v1/start/glossary#product) and backend API key; do not set [Live](/docs/v1/start/glossary#live) confirmation for this test.

## Install and check

1. Download the [complete reference archive](/docs/downloads/store-fulfillment.zip).
2. Check its [SHA-256 source manifest](/docs/fulfillment-examples.json).
3. Extract the archive into a private backend project and enter its fulfillment directory.
4. Replace /private example paths with existing owner-only absolute paths for your backend.
5. Run the following installation/check commands.

```bash
php --version
php -m
php -l php/server.php
php -l send-command.php
```

Expected result: Both syntax checks exit 0. php -m lists curl, mbstring, openssl and pdo_sqlite. Confirm the web PHP handler has these extensions too; CLI output alone does not prove that.

Keep credentials in private environment configuration, not in these commands, source files, terminal screenshots or a browser. This source package has no hidden service installation step and does not alter platform billing.

## Start the receiver

```bash
php -S 127.0.0.1:8112 php/server.php
```

Expected result: the development receiver listens at HTTP 127.0.0.1:8112. It does not expose a public HTTPS service. For Node/Python/C#, KDP_FULFILLMENT_PORT selects another loopback port; PHP takes the port in its -S command. Keep this process running while sending a command from a separate terminal.

Follow [send to the private receiver](/docs/v1/guides/any-store#send-to-the-private-receiver) with your verified private command file. The supplied sender requires PHP/cURL; it is not provider payment verification. A first valid receipt is accepted:true and duplicate:false. No [license](/docs/v1/start/glossary#license) is created until work runs.

## Run the worker

```bash
php php/server.php work
```

Expected result for one new authorized issue: processed is 1 and states contains DONE:1. A completed unchanged replay processes 0 new effects. On uncertain/failed API outcomes, inspect REVIEW or IN_FLIGHT instead of assuming the operation did nothing. No full [license key](/docs/v1/start/glossary#license-key) is printed.

The worker uses nonblocking POSIX flock on worker.lock. Another active worker is refused. An interrupted API call leaves its effect/order held; releasing the operating-system lock is not permission to retry the mutation.

Each run handles at most 25 ready effects and pauses 1.1 seconds after an attempt. The durable state must persist across cron invocations and deployments. Do not mix language implementations on the same state directory or clear it when rotating the scoped API key.

## Inspect and export

```bash
php php/server.php inspect
```

Expected result: states maps each existing effect state to its count. Inspection does not retry an API operation or print a complete key.

```bash
php php/server.php export YOUR_ORDER_ID /private/key.json
```

Expected result after verified DONE issuance and with a new private target path: exported:true and contains_secret:true. The file contains license_id, license_key and email. Export refuses overwrite. This confirms a file was written, not that an email was sent. Authenticate the intended recipient and use your private delivery workflow; never place the export in public_html.

## Complete source

The code below is included from the exact files executed by the Sandbox test. Common config.example.json, command.example.json, send-command.php and CONTRACT.md are in the same archive. The parent guide defines their required fields and security boundary in full.

```php
<?php
declare(strict_types=1);
// Trusted fulfillment command receiver/worker, not a payment gateway or license client.
// PHP 8.2+, cURL, PDO SQLite, mbstring, OpenSSL AES-256-GCM; POSIX private state and flock.
// Private env: KDP_FULFILLMENT_CONFIG/STATE/SECRET/OUTBOX_KEY and KDP_API_KEY.
// CLI: php server.php work | inspect | export YOUR_ORDER_ID /private/key.json
// Local HTTP: php -S 127.0.0.1:8112 server.php; POST /store-event. Expected safe JSON.
ini_set('display_errors','0');
set_error_handler(static function(int $severity,string $message,string $file,int $line):never{throw new ErrorException('Private runtime failure',0,$severity);});

final class CommandRefusal extends RuntimeException {}

function commandJson(mixed $value): string
{
    $sort=static function(mixed $item)use(&$sort):mixed{
        if(is_array($item)){if(!array_is_list($item))ksort($item,SORT_STRING);foreach($item as$key=>$child)$item[$key]=$sort($child);}
        return $item;
    };
    return json_encode($sort($value),JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR);
}
function commandId(mixed $value): bool{return is_string($value)&&preg_match('/\A[A-Za-z0-9_.:-]{1,190}\z/D',$value)===1;}
function commandIdentity(string ...$values): string{return hash('sha256',implode("\0",$values));}

final class FulfillmentReference
{
    public const LIMIT=262144;
    private PDO $db;
    private array $config;
    private string $directory;
    private string $secret;
    private string $outboxKey;
    private string $environment;
    private string $apiKey;
    private string $base;
    private bool $local;

    public function __construct()
    {
        umask(0077);$path=(string)getenv('KDP_FULFILLMENT_CONFIG');
        if(!str_starts_with($path,'/'))throw new CommandRefusal('PRIVATE_ABSOLUTE_CONFIG_REQUIRED');
        $this->config=json_decode((string)file_get_contents($path),true,64,JSON_THROW_ON_ERROR);
        $this->directory=rtrim((string)getenv('KDP_FULFILLMENT_STATE'),'/');
        if(!str_starts_with($this->directory,'/')||is_link($this->directory))throw new CommandRefusal('PRIVATE_ABSOLUTE_STATE_REQUIRED');
        if(!is_dir($this->directory))mkdir($this->directory,0700,true);
        if((fileperms($this->directory)&0077)!==0)throw new CommandRefusal('PRIVATE_STATE_PERMISSIONS_REQUIRED');
        $this->secret=(string)getenv('KDP_FULFILLMENT_SECRET');$key=base64_decode((string)getenv('KDP_FULFILLMENT_OUTBOX_KEY'),true);
        if(strlen($this->secret)<32||$key===false||strlen($key)!==32)throw new CommandRefusal('PRIVATE_SECRETS_REQUIRED');
        $this->outboxKey=$key;$this->environment=$this->config['environment'];
        if(!in_array($this->environment,['sandbox','live'],true))throw new CommandRefusal('INVALID_ENVIRONMENT');
        if($this->environment==='live'&&getenv('KDP_CONFIRM_LIVE')!=='1')throw new CommandRefusal('LIVE_CONFIRMATION_REQUIRED');
        foreach(['source','seller_id','product_id']as$field)if(!commandId($this->config[$field]??null))throw new CommandRefusal('INVALID_CONFIGURATION');
        if(!is_array($this->config['offers']??null)||!$this->config['offers'])throw new CommandRefusal('OFFERS_REQUIRED');
        foreach($this->config['offers']as$id=>$offer){if(!commandId($id)||!commandId($offer['plan_id']??null)||!is_int($offer['renewal_days']??null)||$offer['renewal_days']<1||$offer['renewal_days']>3650)throw new CommandRefusal('INVALID_OFFER_MAPPING');}
        $this->base=rtrim($this->config['base_url'],'/');$url=parse_url($this->base);
        $this->local=$this->environment==='sandbox'&&getenv('KDP_ALLOW_LOOPBACK')==='1'&&($url['scheme']??'')==='http'&&($url['host']??'')==='127.0.0.1';
        if(!$url||empty($url['host'])||isset($url['user'])||isset($url['pass'])||isset($url['query'])||isset($url['fragment'])||!empty($url['path'])||(($url['scheme']??'')!=='https'&&!$this->local))throw new CommandRefusal('INVALID_BASE_URL');
        $this->apiKey=(string)getenv('KDP_API_KEY');if(!str_starts_with($this->apiKey,'lmp_'.$this->environment.'_'))throw new CommandRefusal('API_KEY_ENVIRONMENT_MISMATCH');
        $dbPath=$this->directory.'/fulfillment.sqlite';if(is_link($dbPath))throw new CommandRefusal('INVALID_STATE_FILE');
        $this->db=new PDO('sqlite:'.$dbPath,null,null,[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC]);
        chmod($dbPath,0600);$this->db->exec('PRAGMA busy_timeout=3000');
        $this->db->exec("CREATE TABLE IF NOT EXISTS inbox(event_id TEXT PRIMARY KEY,body_hash TEXT NOT NULL,effect_key TEXT NOT NULL,received_at INTEGER NOT NULL);
            CREATE TABLE IF NOT EXISTS orders(order_key TEXT PRIMARY KEY,offer_id TEXT NOT NULL,email TEXT NOT NULL,license_id TEXT,last_sequence INTEGER NOT NULL DEFAULT 0,held INTEGER NOT NULL DEFAULT 0);
            CREATE TABLE IF NOT EXISTS effects(effect_key TEXT PRIMARY KEY,order_key TEXT NOT NULL,sequence INTEGER NOT NULL,command_hash TEXT NOT NULL,command_json TEXT NOT NULL,state TEXT NOT NULL,request_id TEXT,error_code TEXT,created_at INTEGER NOT NULL,UNIQUE(order_key,sequence));
            CREATE TABLE IF NOT EXISTS outbox(order_key TEXT PRIMARY KEY,license_id TEXT NOT NULL,cipher TEXT NOT NULL,created_at INTEGER NOT NULL);
            CREATE TABLE IF NOT EXISTS binding(id INTEGER PRIMARY KEY CHECK(id=1),config_hash TEXT NOT NULL,outbox_key_hash TEXT NOT NULL);");
        $hash=hash('sha256',commandJson($this->config));$keyHash=hash('sha256',$this->outboxKey);
        $this->execute('INSERT OR IGNORE INTO binding(id,config_hash,outbox_key_hash) VALUES(1,?,?)',[$hash,$keyHash]);
        $binding=$this->one('SELECT * FROM binding WHERE id=1');
        if($binding['config_hash']!==$hash||$binding['outbox_key_hash']!==$keyHash)throw new CommandRefusal('CONFIGURATION_CHANGED_RECONCILE_FIRST');
    }

    private function execute(string $sql,array $values=[]): PDOStatement{$statement=$this->db->prepare($sql);$statement->execute($values);return $statement;}
    private function one(string $sql,array $values=[]): array|false{return $this->execute($sql,$values)->fetch();}
    private function transaction(callable $operation): mixed
    {
        $this->db->exec('BEGIN IMMEDIATE');
        try{$result=$operation();$this->db->exec('COMMIT');return $result;}catch(Throwable $error){$this->db->exec('ROLLBACK');throw $error;}
    }

    private function validate(array $event): array
    {
        $fields=['version','source','seller_id','environment','event_id','effect_id','order_id','sequence','action','offer_id','customer','expected_status','expected_expires_at'];
        $keys=array_keys($event);sort($fields);sort($keys);
        if($keys!==$fields||($event['version']??null)!==1)throw new CommandRefusal('INVALID_COMMAND');
        foreach(['source','seller_id','environment']as$field)if($event[$field]!==$this->config[$field])throw new CommandRefusal('WRONG_SELLER_OR_ENVIRONMENT');
        foreach(['event_id','effect_id','order_id','offer_id']as$field)if(!commandId($event[$field]))throw new CommandRefusal('INVALID_IDENTIFIER');
        if(!isset($this->config['offers'][$event['offer_id']])||!in_array($event['action'],['issue','renew','suspend','restore','revoke','record_cancellation'],true)||!is_int($event['sequence'])||$event['sequence']<1||$event['sequence']>2147483647)throw new CommandRefusal('UNSUPPORTED_COMMAND');
        $customer=$event['customer'];$customerKeys=is_array($customer)?array_keys($customer):[];sort($customerKeys);
        if($customerKeys!==['email','name']||!is_string($customer['email'])||!filter_var($customer['email'],FILTER_VALIDATE_EMAIL)||strlen($customer['email'])>190||!is_string($customer['name'])||mb_strlen($customer['name'])>160)throw new CommandRefusal('INVALID_CUSTOMER');
        if($event['expected_expires_at']!==null&&(!is_string($event['expected_expires_at'])||strlen($event['expected_expires_at'])>40))throw new CommandRefusal('INVALID_EXPECTED_EXPIRY');
        if($event['action']==='issue'){
            if($event['sequence']!==1||$event['expected_status']!==null||$event['expected_expires_at']!==null)throw new CommandRefusal('INVALID_ISSUANCE_COMMAND');
        }elseif(!in_array($event['expected_status'],['active','suspended','revoked','expired','pending','cancelled'],true))throw new CommandRefusal('CURRENT_STATE_REQUIRED');
        return $event;
    }

    public function accept(string $body,string $signature): array
    {
        if(strlen($body)>self::LIMIT||!preg_match('/\At=([1-9][0-9]{0,10}),v1=([0-9a-f]{64})\z/D',$signature,$match)||abs(time()-(int)$match[1])>300||!hash_equals(hash_hmac('sha256',$match[1].'.'.$body,$this->secret),$match[2]))throw new CommandRefusal('INVALID_SIGNATURE');
        $decoded=json_decode($body,false,64,JSON_THROW_ON_ERROR);if(!is_object($decoded))throw new CommandRefusal('INVALID_COMMAND');
        $event=$this->validate(json_decode($body,true,64,JSON_THROW_ON_ERROR));
        $orderKey=commandIdentity($event['source'],$event['seller_id'],$event['environment'],$event['order_id']);
        $effectKey=commandIdentity($event['source'],$event['seller_id'],$event['environment'],$event['effect_id']);
        $semantic=$event;unset($semantic['event_id']);$json=commandJson($semantic);$commandHash=hash('sha256',$json);$rawHash=hash('sha256',$body);
        return $this->transaction(function()use($event,$orderKey,$effectKey,$json,$commandHash,$rawHash):array{
            $previous=$this->one('SELECT body_hash FROM inbox WHERE event_id=?',[$event['event_id']]);
            if($previous){if($previous['body_hash']!==$rawHash)throw new CommandRefusal('EVENT_ID_CONFLICT');return ['accepted'=>true,'duplicate'=>true];}
            $effect=$this->one('SELECT command_hash FROM effects WHERE effect_key=?',[$effectKey]);
            if($effect&&$effect['command_hash']!==$commandHash)throw new CommandRefusal('EFFECT_CONFLICT');
            $order=$this->one('SELECT * FROM orders WHERE order_key=?',[$orderKey]);
            if($order&&($order['offer_id']!==$event['offer_id']||$order['email']!==$event['customer']['email']))throw new CommandRefusal('ORDER_MAPPING_CONFLICT');
            $this->execute('INSERT OR IGNORE INTO orders(order_key,offer_id,email) VALUES(?,?,?)',[$orderKey,$event['offer_id'],$event['customer']['email']]);
            if(!$effect){
                try{$this->execute("INSERT INTO effects(effect_key,order_key,sequence,command_hash,command_json,state,created_at) VALUES(?,?,?,?,?,'WAITING',?)",[$effectKey,$orderKey,$event['sequence'],$commandHash,$json,time()]);}
                catch(PDOException $error){if($error->getCode()==='23000')throw new CommandRefusal('SEQUENCE_CONFLICT');throw $error;}
            }
            $this->execute('INSERT INTO inbox(event_id,body_hash,effect_key,received_at) VALUES(?,?,?,?)',[$event['event_id'],$rawHash,$effectKey,time()]);
            return ['accepted'=>true,'duplicate'=>(bool)$effect];
        });
    }

    private function api(string $method,string $path,?array $body=null,?string $idempotency=null): array
    {
        $headers=['Authorization: Bearer '.$this->apiKey,'Accept: application/json','Content-Type: application/json','User-Agent: KDPika-Reference-Fulfillment/1'];
        if($idempotency!==null)$headers[]='Idempotency-Key: '.$idempotency;
        $raw='';$curl=curl_init($this->base.'/api/v1'.$path);
        $options=[CURLOPT_CUSTOMREQUEST=>$method,CURLOPT_HTTPHEADER=>$headers,CURLOPT_FOLLOWLOCATION=>false,CURLOPT_CONNECTTIMEOUT=>5,CURLOPT_TIMEOUT=>15,
            CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2,CURLOPT_PROTOCOLS=>$this->local?CURLPROTO_HTTP:CURLPROTO_HTTPS,
            CURLOPT_WRITEFUNCTION=>static function($handle,string $bytes)use(&$raw):int{if(strlen($raw)+strlen($bytes)>1048576)return 0;$raw.=$bytes;return strlen($bytes);}];
        if($body!==null)$options[CURLOPT_POSTFIELDS]=commandJson($body);
        curl_setopt_array($curl,$options);$sent=curl_exec($curl);$status=(int)curl_getinfo($curl,CURLINFO_RESPONSE_CODE);
        if($sent===false)throw new CommandRefusal('UNKNOWN_API_RESULT');
        $data=json_decode($raw,true,64,JSON_THROW_ON_ERROR);if(!is_array($data))throw new CommandRefusal('UNEXPECTED_API_BODY');return [$status,$data];
    }

    private function perform(array $command,array $order,string $effectKey): array
    {
        $action=$command['action'];
        if($action==='issue'){
            if($order['license_id'])throw new CommandRefusal('ORDER_ALREADY_HAS_LICENSE');
            $offer=$this->config['offers'][$command['offer_id']];
            $idempotency=($this->config['create_idempotency']??'effect_hash')==='effect_id'?$command['effect_id']:'store_'.$effectKey;
            if(strlen($idempotency)<8||strlen($idempotency)>190)throw new CommandRefusal('INVALID_CREATE_IDEMPOTENCY');
            [$status,$result]=$this->api('POST','/licenses',['product_id'=>$this->config['product_id'],'plan_id'=>$offer['plan_id'],'customer'=>$command['customer']],$idempotency);
            $item=$result['license']??[];
            if($status!==201||($result['success']??null)!==true||!is_string($item['id']??null)||!preg_match('/\Alic_[A-Za-z0-9_-]{1,100}\z/D',$item['id'])||!is_string($item['license_key']??null)||strlen($item['license_key'])<12||strlen($item['license_key'])>100||($item['environment']??null)!==$this->environment)throw new CommandRefusal('ISSUANCE_RESULT_REQUIRES_REVIEW');
            return [$item['id'],$item['license_key'],$result['request_id']??null];
        }
        if(!$order['license_id'])throw new CommandRefusal('ISSUE_FIRST');
        $id=$order['license_id'];$path='/licenses/'.rawurlencode($id);[$status,$current]=$this->api('GET',$path);$item=$current['license']??[];
        if($status!==200||($current['success']??null)!==true||($item['id']??null)!==$id||($item['environment']??null)!==$this->environment||($item['status']??null)!==$command['expected_status']||($item['expires_at']??null)!==$command['expected_expires_at'])throw new CommandRefusal('LICENSE_CHANGED_RECONCILE_FIRST');
        if($action==='record_cancellation')return [$id,null,$current['request_id']??null];
        $body=$action==='renew'?['days'=>$this->config['offers'][$command['offer_id']]['renewal_days']]:['reason'=>'Verified store fulfillment decision'];
        [$status,$changed]=$this->api('POST',$path.'/'.$action,$body);$value=$changed['license']??[];
        $expected=match($action){'suspend'=>'suspended','restore'=>'active','revoke'=>'revoked','renew'=>$item['status']==='expired'?'active':$item['status']};
        if($status!==200||($changed['success']??null)!==true||($value['id']??null)!==$id||($value['status']??null)!==$expected||($action==='renew'&&!is_string($value['expires_at']??null)))throw new CommandRefusal('MUTATION_RESULT_REQUIRES_REVIEW');
        return [$id,null,$changed['request_id']??null];
    }

    public function work(): array
    {
        $path=$this->directory.'/worker.lock';if(is_link($path))throw new CommandRefusal('INVALID_LOCK_FILE');
        $lock=fopen($path,'c');if(!$lock||!flock($lock,LOCK_EX|LOCK_NB))throw new CommandRefusal('WORKER_BUSY');
        $processed=0;
        try{
            while($processed<25){
                $claimed=$this->transaction(function():?array{
                    $effect=$this->one("SELECT e.* FROM effects e JOIN orders o ON o.order_key=e.order_key WHERE e.state='WAITING' AND o.held=0 AND e.sequence=o.last_sequence+1 ORDER BY e.created_at,e.effect_key LIMIT 1");
                    if(!$effect)return null;$order=$this->one('SELECT * FROM orders WHERE order_key=?',[$effect['order_key']]);
                    $this->execute("UPDATE effects SET state='IN_FLIGHT' WHERE effect_key=?",[$effect['effect_key']]);$this->execute('UPDATE orders SET held=1 WHERE order_key=?',[$effect['order_key']]);
                    return [$effect,$order];
                });
                if($claimed===null)break;[$effect,$order]=$claimed;
                try{
                    [$id,$key,$requestId]=$this->perform(json_decode($effect['command_json'],true,64,JSON_THROW_ON_ERROR),$order,$effect['effect_key']);
                    if($requestId!==null&&(!is_string($requestId)||strlen($requestId)>190))throw new CommandRefusal('UNEXPECTED_REQUEST_ID');
                    $this->transaction(function()use($effect,$id,$key,$requestId):void{
                        if($key!==null){
                            $nonce=random_bytes(12);$tag='';$cipher=openssl_encrypt($key,'aes-256-gcm',$this->outboxKey,OPENSSL_RAW_DATA,$nonce,$tag,$effect['order_key'],16);
                            if($cipher===false)throw new CommandRefusal('OUTBOX_ENCRYPTION_FAILED');
                            $this->execute('INSERT INTO outbox(order_key,license_id,cipher,created_at) VALUES(?,?,?,?)',[$effect['order_key'],$id,base64_encode($nonce.$cipher.$tag),time()]);
                        }
                        $this->execute("UPDATE effects SET state='DONE',request_id=? WHERE effect_key=?",[$requestId,$effect['effect_key']]);
                        $this->execute('UPDATE orders SET license_id=?,last_sequence=?,held=0 WHERE order_key=?',[$id,$effect['sequence'],$effect['order_key']]);
                    });
                }catch(Throwable){$this->execute("UPDATE effects SET state='REVIEW',error_code='RECONCILE_BEFORE_RETRY' WHERE effect_key=?",[$effect['effect_key']]);}
                $processed++;usleep(1100000);
            }
        }finally{flock($lock,LOCK_UN);fclose($lock);}
        return ['processed'=>$processed,'states'=>$this->inspect()];
    }

    public function inspect(): array
    {
        $states=[];foreach($this->execute('SELECT state,COUNT(*) AS count FROM effects GROUP BY state')->fetchAll()as$row)$states[$row['state']]=(int)$row['count'];return $states;
    }

    public function export(string $orderId,string $target): array
    {
        if(!commandId($orderId))throw new CommandRefusal('INVALID_ORDER_ID');
        $orderKey=commandIdentity($this->config['source'],$this->config['seller_id'],$this->environment,$orderId);
        $row=$this->one('SELECT b.*,o.email FROM outbox b JOIN orders o ON o.order_key=b.order_key WHERE b.order_key=?',[$orderKey]);if(!$row)throw new CommandRefusal('NO_SAVED_KEY');
        $parent=dirname($target);if(!str_starts_with($target,'/')||!is_dir($parent)||(fileperms($parent)&0077)!==0)throw new CommandRefusal('PRIVATE_EXPORT_DIRECTORY_REQUIRED');
        $raw=base64_decode($row['cipher'],true);if($raw===false||strlen($raw)<29)throw new CommandRefusal('INVALID_OUTBOX');
        $key=openssl_decrypt(substr($raw,12,-16),'aes-256-gcm',$this->outboxKey,OPENSSL_RAW_DATA,substr($raw,0,12),substr($raw,-16),$orderKey);
        if($key===false)throw new CommandRefusal('OUTBOX_DECRYPTION_FAILED');
        $stream=fopen($target,'xb');if(!$stream)throw new CommandRefusal('EXPORT_EXISTS');
        try{chmod($target,0600);$json=commandJson(['license_id'=>$row['license_id'],'license_key'=>$key,'email'=>$row['email']]);if(fwrite($stream,$json)!==strlen($json))throw new CommandRefusal('EXPORT_WRITE_FAILED');}finally{fclose($stream);}
        return ['exported'=>true,'contains_secret'=>true];
    }
}

function referenceReply(int $status,array $value): never
{
    http_response_code($status);header('Content-Type: application/json');header('Cache-Control: no-store');header('X-Content-Type-Options: nosniff');echo commandJson($value);exit;
}

if(defined('KDP_FULFILLMENT_LIBRARY_ONLY')&&KDP_FULFILLMENT_LIBRARY_ONLY===true)return;

try{
    $app=new FulfillmentReference();
    if(PHP_SAPI==='cli'){
        if(($argc??0)===2&&$argv[1]==='work')$output=$app->work();
        elseif(($argc??0)===2&&$argv[1]==='inspect')$output=['states'=>$app->inspect()];
        elseif(($argc??0)===4&&$argv[1]==='export')$output=$app->export($argv[2],$argv[3]);
        else throw new CommandRefusal('USE_WORK_INSPECT_OR_EXPORT');
        echo commandJson($output).PHP_EOL;exit;
    }
    if(($_SERVER['REQUEST_METHOD']??'')!=='POST'||($_SERVER['REQUEST_URI']??'')!=='/store-event')referenceReply(404,['accepted'=>false]);
    $length=$_SERVER['CONTENT_LENGTH']??'';
    if(!ctype_digit((string)$length)||isset($_SERVER['HTTP_TRANSFER_ENCODING']))referenceReply(400,['accepted'=>false]);
    if((int)$length>FulfillmentReference::LIMIT)referenceReply(413,['accepted'=>false]);
    if(strtolower(trim(explode(';',$_SERVER['CONTENT_TYPE']??'')[0]))!=='application/json')referenceReply(415,['accepted'=>false]);
    $signature=$_SERVER['HTTP_X_FULFILLMENT_SIGNATURE']??'';
    if(!is_string($signature)||!preg_match('/\At=[1-9][0-9]{0,10},v1=[0-9a-f]{64}\z/D',$signature))referenceReply(401,['accepted'=>false]);
    $body=file_get_contents('php://input',false,null,0,FulfillmentReference::LIMIT+1);
    if($body===false||strlen($body)!==(int)$length)referenceReply(400,['accepted'=>false]);
    referenceReply(202,$app->accept($body,$signature));
}catch(Throwable $error){
    if(PHP_SAPI==='cli'){echo '{"completed":false,"error":"REVIEW_PRIVATE_CONFIGURATION_OR_STATE"}'.PHP_EOL;exit(1);}
    $status=$error instanceof CommandRefusal?($error->getMessage()==='INVALID_SIGNATURE'?401:409):($error instanceof JsonException?400:503);
    referenceReply($status,['accepted'=>false]);
}
```

## Expected result

The actual local receiver and worker pass the shared lifecycle suite: durable receipt before work, authentic command acceptance, tamper/time/mode/mapping refusal, duplicate/conflicting effects, ordered gap handling, real creation and recipient [activation](/docs/v1/start/glossary#activation), renewal, suspension, restoration and terminal revocation. The suite also exports an encrypted saved key, refuses unsafe config/key drift, restarts the receiver, holds a simulated interrupted intent, and hides a genuinely successful remote create response behind a failing gateway. No second create is sent after that unknown result.

These are exact-source local Sandbox tests on macOS. They are not an external store purchase, production deployment, native Windows test, email delivery test or promise of cross-system exactly-once behavior. The known API replay limitations remain unchanged. The read-before-write expected-state check is not an atomic remote compare-and-swap.

## Common mistakes

- Sending a provider's native event directly here: this receiver accepts your verified adapter's private command format, not a Stripe/Paddle/PayPal callback.
- Exposing the development server publicly without TLS, request bounds and rate protection.
- Treating a 202 receipt or exported key file as proof of successful buyer email delivery.
- Reusing a fresh state directory after an unknown create instead of reconciling the original order.

## Security notes

- Keep config, state, encrypted-ledger backups and plaintext exports outside all web roots.
- A backend that holds both the encryption key and database can decrypt its outbox; filesystem and process isolation still matter.
- Keep one reviewed writer per order and preserve stable business-effect identity across delivery retries.
- PHP SAPI can buffer bodies before script execution. Configure the ingress 256 KiB body limit and timeouts, not only application validation.
- Recheck runtime support and dependency advisories before production deployment. The recorded advisory scan is not certification.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| Safe configuration error | Missing/mismatched private mapping, key, permissions or runtime dependency | Verify private environment and required modules; never publish raw secrets to debug |
| HTTP 401 | Wrong signature, timestamp, changed bytes or duplicate signature header | Sign exact bytes with the separate command secret and verify the clock |
| HTTP 409 | Mapping/merchant/effect/sequence conflict | Reconcile trusted adapter state; do not invent a different effect ID |
| No work processed | Already complete, sequence gap, held order or busy worker | Inspect saved state and authorized history before any write |
| Export refused | Wrong order/key, unsafe target directory or file already exists | Verify recipient/order and use a new private owner-only target file |

## Next steps

Review [the parent security checklist](/docs/v1/guides/any-store#security-notes), then test your real provider-specific adapter separately in its test environment.

## Related

- [Generic store mapping and lifecycle](/docs/v1/guides/any-store)
- [API idempotency](/docs/v1/api/idempotency)
- [Manual delivery and customer lookup](/docs/v1/guides/manual-issuance)
