KDPika Licensing Sign inStart free
API v1
Show all languages
Browse documentation

Choose a page in the navigation drawer.

KDPika Licensing · API v1 Advanced

C# store fulfillment reference

Run the complete C# trusted-command receiver and durable licensing worker with private state, encrypted key export and Sandbox evidence.

On this page
Updated 2026-10-18 UTCDownload Markdown

Run the complete C# 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.

Prerequisites

  • Read the complete store backend protocol and mapping guide.
  • .NET 10 SDK/runtime, POSIX libc flock and Microsoft.Data.Sqlite 10.0.13 with the supplied transitive lock. This reference explicitly refuses native Windows.
  • 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 product and backend API key; do not set Live confirmation for this test.

Install and check

  1. Download the complete reference archive.
  2. Check its SHA-256 source manifest.
  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.
bashbash
dotnet --version
dotnet restore csharp/Fulfillment.csproj --locked-mode
dotnet build csharp/Fulfillment.csproj --no-restore --configuration Release --output /private/kdpika-fulfillment-build

Expected result: Restore honors packages.lock.json; the build exits 0. The tested .NET 10 build has zero warnings and errors. The archive contains source and lock metadata, not a prebuilt host-specific binary.

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

bashbash
dotnet /private/kdpika-fulfillment-build/Fulfillment.dll serve

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 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 is created until work runs.

Run the worker

bashbash
dotnet /private/kdpika-fulfillment-build/Fulfillment.dll 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 is printed.

The worker uses libc flock through a small POSIX interop boundary. It refuses a competing worker. A crashed API attempt remains IN_FLIGHT/held and is never automatically repeated. The explicit POSIX restriction avoids pretending that native Windows ACLs/locking were tested.

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

bashbash
dotnet /private/kdpika-fulfillment-build/Fulfillment.dll inspect

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

bashbash
dotnet /private/kdpika-fulfillment-build/Fulfillment.dll 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.

Fulfillment.csprojxml
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.13" />
  </ItemGroup>
</Project>
Program.cscsharp
// Trusted fulfillment receiver/worker; not a payment gateway or browser client.
// .NET 10, Microsoft.Data.Sqlite 10.0.13, POSIX flock and private SQLite/AES-GCM.
// Set private KDP_FULFILLMENT_CONFIG/STATE/SECRET/OUTBOX_KEY and KDP_API_KEY.
// dotnet Fulfillment.dll serve | work | inspect | export YOUR_ORDER_ID /private/key.json
// Expected: safe receipt/work JSON, full keys only in explicit private exports.
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.Data.Sqlite;

static class Program
{
    [SupportedOSPlatform("linux")]
    [SupportedOSPlatform("macos")]
    public static async Task<int> Main(string[] args)
    {
        try
        {
            if (!OperatingSystem.IsLinux() && !OperatingSystem.IsMacOS())
                throw new PlatformNotSupportedException("POSIX_REFERENCE_REQUIRED");
            using var app = new Fulfillment();
            object result;
            if (args is ["serve"]) { await app.Serve(); return 0; }
            if (args is ["work"]) result = await app.Work();
            else if (args is ["inspect"]) result = new { states = app.Inspect() };
            else if (args is ["export", var order, var target]) result = app.Export(order, target);
            else throw new Refusal("USE_SERVE_WORK_INSPECT_OR_EXPORT");
            Console.WriteLine(JsonSerializer.Serialize(result, Fulfillment.JsonOptions));
            return 0;
        }
        catch
        {
            Console.WriteLine("{\"completed\":false,\"error\":\"REVIEW_PRIVATE_CONFIGURATION_OR_STATE\"}");
            return 1;
        }
    }
}

sealed class Refusal(string code) : Exception(code) { }

[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]
sealed class Fulfillment : IDisposable
{
    public const int Limit = 262144;
    public static readonly JsonSerializerOptions JsonOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
    static readonly UTF8Encoding Utf8 = new(false, true);
    static readonly Regex Identifier = new(@"\A[A-Za-z0-9_.:-]{1,190}\z", RegexOptions.CultureInvariant);
    readonly SqliteConnection db;
    readonly JsonObject config;
    readonly string directory, secret, environment, apiKey, baseUrl;
    readonly byte[] outboxKey;
    readonly object receiverLock = new();
    readonly HttpClient http = new(new SocketsHttpHandler { AllowAutoRedirect = false, ConnectTimeout = TimeSpan.FromSeconds(5) }) { Timeout = TimeSpan.FromSeconds(15) };
    [DllImport("libc", SetLastError = true)] static extern uint umask(uint mask);
    [DllImport("libc", SetLastError = true)] static extern int flock(int descriptor, int operation);

    static string Env(string name) => Environment.GetEnvironmentVariable(name) ?? "";
    static string Hash(byte[] value) => Convert.ToHexString(SHA256.HashData(value)).ToLowerInvariant();
    static string Hash(string value) => Hash(Utf8.GetBytes(value));
    static string Identity(params string[] values) => Hash(string.Join('\0', values));
    static string Text(JsonNode? value) => value?.GetValue<string>() ?? throw new Refusal("STRING_REQUIRED");
    static bool IsId(string value) => Identifier.IsMatch(value);
    static long Now() => DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    static string? StringValue(Dictionary<string, object?> item, string key) => item[key] as string;
    static string Encode(JsonNode? node)
    {
        JsonNode? Sort(JsonNode? item) => item switch
        {
            JsonObject obj => new JsonObject(obj.OrderBy(pair => pair.Key, StringComparer.Ordinal).Select(pair => new KeyValuePair<string, JsonNode?>(pair.Key, Sort(pair.Value)))),
            JsonArray list => new JsonArray(list.Select(Sort).ToArray()),
            _ => item?.DeepClone()
        };
        return Sort(node)?.ToJsonString(JsonOptions) ?? "null";
    }
    static void Require(bool condition, string code) { if (!condition) throw new Refusal(code); }
    static void PrivateDirectory(string value)
    {
        Require(Path.IsPathFullyQualified(value), "PRIVATE_ABSOLUTE_DIRECTORY_REQUIRED");
        if (Directory.Exists(value)) Require(new DirectoryInfo(value).LinkTarget is null, "SYMLINK_DIRECTORY_REFUSED");
        Directory.CreateDirectory(value, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
        Require(((int)File.GetUnixFileMode(value) & 0x3F) == 0, "PRIVATE_DIRECTORY_PERMISSIONS_REQUIRED");
    }

    public Fulfillment()
    {
        Require(OperatingSystem.IsLinux() || OperatingSystem.IsMacOS(), "POSIX_REFERENCE_REQUIRED");
        umask(0x3F);
        var configFile = Env("KDP_FULFILLMENT_CONFIG");
        Require(Path.IsPathFullyQualified(configFile), "PRIVATE_ABSOLUTE_CONFIG_REQUIRED");
        config = JsonNode.Parse(File.ReadAllBytes(configFile)) as JsonObject ?? throw new Refusal("INVALID_CONFIG");
        directory = Env("KDP_FULFILLMENT_STATE"); PrivateDirectory(directory);
        secret = Env("KDP_FULFILLMENT_SECRET"); outboxKey = Convert.FromBase64String(Env("KDP_FULFILLMENT_OUTBOX_KEY"));
        Require(secret.Length >= 32 && outboxKey.Length == 32, "PRIVATE_SECRETS_REQUIRED");
        environment = Text(config["environment"]);
        Require(environment is "sandbox" or "live", "INVALID_ENVIRONMENT");
        Require(environment != "live" || Env("KDP_CONFIRM_LIVE") == "1", "LIVE_CONFIRMATION_REQUIRED");
        foreach (var name in new[] { "source", "seller_id", "product_id" }) Require(IsId(Text(config[name])), "INVALID_CONFIGURATION");
        Require(config["offers"] is JsonObject { Count: > 0 }, "OFFERS_REQUIRED");
        foreach (var offer in (JsonObject)config["offers"]!)
        {
            Require(IsId(offer.Key) && IsId(Text(offer.Value?["plan_id"])), "INVALID_OFFER_MAPPING");
            var days = offer.Value?["renewal_days"]?.GetValue<int>() ?? 0;
            Require(days is >= 1 and <= 3650, "INVALID_OFFER_MAPPING");
        }
        baseUrl = Text(config["base_url"]).TrimEnd('/');
        var url = new Uri(baseUrl, UriKind.Absolute);
        var local = environment == "sandbox" && Env("KDP_ALLOW_LOOPBACK") == "1" && url.Scheme == "http" && url.Host == "127.0.0.1";
        Require(url.UserInfo == "" && url.Query == "" && url.Fragment == "" && url.AbsolutePath == "/" && url.Host != "" && (url.Scheme == "https" || local), "INVALID_BASE_URL");
        apiKey = Env("KDP_API_KEY"); Require(apiKey.StartsWith("lmp_" + environment + "_", StringComparison.Ordinal), "API_KEY_ENVIRONMENT_MISMATCH");
        var database = Path.Combine(directory, "fulfillment.sqlite");
        Require(new FileInfo(database).LinkTarget is null, "INVALID_STATE_FILE");
        db = new SqliteConnection(new SqliteConnectionStringBuilder { DataSource = database, Pooling = false, DefaultTimeout = 3 }.ToString());
        db.Open(); File.SetUnixFileMode(database, UnixFileMode.UserRead | UnixFileMode.UserWrite);
        Run("""
            PRAGMA busy_timeout=3000;
            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);
            """);
        var configHash = Hash(Encode(config)); var keyHash = Hash(outboxKey);
        Run("INSERT OR IGNORE INTO binding(id,config_hash,outbox_key_hash) VALUES(1,@p0,@p1)", configHash, keyHash);
        var bound = One("SELECT * FROM binding WHERE id=1")!;
        Require(StringValue(bound, "config_hash") == configHash && StringValue(bound, "outbox_key_hash") == keyHash, "CONFIGURATION_CHANGED_RECONCILE_FIRST");
    }

    SqliteCommand Command(string sql, params object?[] values)
    {
        var command = db.CreateCommand(); command.CommandText = sql;
        for (var i = 0; i < values.Length; i++) command.Parameters.AddWithValue("@p" + i, values[i] ?? DBNull.Value);
        return command;
    }
    void Run(string sql, params object?[] values) { using var command = Command(sql, values); command.ExecuteNonQuery(); }
    Dictionary<string, object?>? One(string sql, params object?[] values)
    {
        using var command = Command(sql, values); using var reader = command.ExecuteReader();
        if (!reader.Read()) return null;
        return Enumerable.Range(0, reader.FieldCount).ToDictionary(reader.GetName, index => reader.IsDBNull(index) ? null : reader.GetValue(index));
    }
    T Transaction<T>(Func<T> operation)
    {
        Run("BEGIN IMMEDIATE");
        try { var result = operation(); Run("COMMIT"); return result; }
        catch { Run("ROLLBACK"); throw; }
    }

    JsonObject Validate(JsonObject command)
    {
        var fields = new[] { "version", "source", "seller_id", "environment", "event_id", "effect_id", "order_id", "sequence", "action", "offer_id", "customer", "expected_status", "expected_expires_at" };
        Require(command.Select(pair => pair.Key).Order().SequenceEqual(fields.Order()) && command["version"]?.GetValue<int>() == 1, "INVALID_COMMAND");
        foreach (var field in new[] { "source", "seller_id", "environment" }) Require(Text(command[field]) == Text(config[field]), "WRONG_SELLER_OR_ENVIRONMENT");
        foreach (var field in new[] { "event_id", "effect_id", "order_id", "offer_id" }) Require(IsId(Text(command[field])), "INVALID_IDENTIFIER");
        Require(((JsonObject)config["offers"]!).ContainsKey(Text(command["offer_id"])), "UNMAPPED_OFFER");
        var action = Text(command["action"]); var sequence = command["sequence"]?.GetValue<int>() ?? 0;
        Require(sequence >= 1 && new[] { "issue", "renew", "suspend", "restore", "revoke", "record_cancellation" }.Contains(action), "UNSUPPORTED_COMMAND");
        Require(command["customer"] is JsonObject customer && customer.Select(pair => pair.Key).Order().SequenceEqual(new[] { "email", "name" }), "INVALID_CUSTOMER");
        var email = Text(command["customer"]?["email"]); var name = Text(command["customer"]?["name"]);
        Require(email.Length <= 190 && Regex.IsMatch(email, @"\A[^\s@]{1,120}@[^\s@]{1,120}\z") && name.EnumerateRunes().Count() <= 160, "INVALID_CUSTOMER");
        Require(command["expected_expires_at"] is null || Text(command["expected_expires_at"]).Length <= 40, "INVALID_EXPECTED_EXPIRY");
        if (action == "issue") Require(sequence == 1 && command["expected_status"] is null && command["expected_expires_at"] is null, "INVALID_ISSUANCE_COMMAND");
        else Require(new[] { "active", "suspended", "revoked", "expired", "pending", "cancelled" }.Contains(Text(command["expected_status"])), "CURRENT_STATE_REQUIRED");
        return command;
    }

    public object Accept(byte[] body, string signature)
    {
        var match = Regex.Match(signature, @"\At=([1-9][0-9]{0,10}),v1=([0-9a-f]{64})\z");
        Require(body.Length <= Limit && match.Success && Math.Abs(Now() - long.Parse(match.Groups[1].Value)) <= 300, "INVALID_SIGNATURE");
        var message = Utf8.GetBytes(match.Groups[1].Value + ".").Concat(body).ToArray();
        Require(CryptographicOperations.FixedTimeEquals(HMACSHA256.HashData(Utf8.GetBytes(secret), message), Convert.FromHexString(match.Groups[2].Value)), "INVALID_SIGNATURE");
        var command = Validate(JsonNode.Parse(Utf8.GetString(body)) as JsonObject ?? throw new Refusal("INVALID_COMMAND"));
        var orderKey = Identity(Text(command["source"]), Text(command["seller_id"]), environment, Text(command["order_id"]));
        var effectKey = Identity(Text(command["source"]), Text(command["seller_id"]), environment, Text(command["effect_id"]));
        var semantic = (JsonObject)command.DeepClone(); semantic.Remove("event_id");
        var json = Encode(semantic); var commandHash = Hash(json); var rawHash = Hash(body);
        return Transaction<object>(() =>
        {
            var previous = One("SELECT body_hash FROM inbox WHERE event_id=@p0", Text(command["event_id"]));
            if (previous is not null) { Require(StringValue(previous, "body_hash") == rawHash, "EVENT_ID_CONFLICT"); return new { accepted = true, duplicate = true }; }
            var effect = One("SELECT command_hash FROM effects WHERE effect_key=@p0", effectKey);
            Require(effect is null || StringValue(effect, "command_hash") == commandHash, "EFFECT_CONFLICT");
            var order = One("SELECT * FROM orders WHERE order_key=@p0", orderKey);
            var offer = Text(command["offer_id"]); var email = Text(command["customer"]?["email"]);
            Require(order is null || (StringValue(order, "offer_id") == offer && StringValue(order, "email") == email), "ORDER_MAPPING_CONFLICT");
            Run("INSERT OR IGNORE INTO orders(order_key,offer_id,email) VALUES(@p0,@p1,@p2)", orderKey, offer, email);
            if (effect is null)
            {
                var sequence = command["sequence"]!.GetValue<int>();
                Require(One("SELECT effect_key FROM effects WHERE order_key=@p0 AND sequence=@p1", orderKey, sequence) is null, "SEQUENCE_CONFLICT");
                Run("INSERT INTO effects(effect_key,order_key,sequence,command_hash,command_json,state,created_at) VALUES(@p0,@p1,@p2,@p3,@p4,'WAITING',@p5)", effectKey, orderKey, sequence, commandHash, json, Now());
            }
            Run("INSERT INTO inbox(event_id,body_hash,effect_key,received_at) VALUES(@p0,@p1,@p2,@p3)", Text(command["event_id"]), rawHash, effectKey, Now());
            return new { accepted = true, duplicate = effect is not null };
        });
    }

    async Task<(int, JsonObject)> Api(HttpMethod method, string route, JsonObject? body = null, string? idempotency = null)
    {
        using var request = new HttpRequestMessage(method, baseUrl + "/api/v1" + route);
        request.Headers.Authorization = new("Bearer", apiKey); request.Headers.Accept.ParseAdd("application/json"); request.Headers.UserAgent.ParseAdd("KDPika-Reference-Fulfillment/1");
        if (idempotency is not null) request.Headers.Add("Idempotency-Key", idempotency);
        if (body is not null) request.Content = new StringContent(Encode(body), Utf8, "application/json");
        using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
        using var response = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, timeout.Token);
        using var input = await response.Content.ReadAsStreamAsync(timeout.Token); using var output = new MemoryStream();
        var buffer = new byte[8192]; int count;
        while ((count = await input.ReadAsync(buffer, timeout.Token)) > 0) { Require(output.Length + count <= 1048576, "UNEXPECTED_API_BODY"); output.Write(buffer, 0, count); }
        return ((int)response.StatusCode, JsonNode.Parse(output.ToArray()) as JsonObject ?? throw new Refusal("UNEXPECTED_API_BODY"));
    }

    async Task<(string Id, string? Key, string? RequestId)> Perform(JsonObject command, Dictionary<string, object?> order, string effectKey)
    {
        var action = Text(command["action"]);
        if (action == "issue")
        {
            Require(order["license_id"] is null, "ORDER_ALREADY_HAS_LICENSE");
            var offer = config["offers"]![Text(command["offer_id"])];
            var (status, result) = await Api(HttpMethod.Post, "/licenses", new JsonObject { ["product_id"] = Text(config["product_id"]), ["plan_id"] = Text(offer?["plan_id"]), ["customer"] = command["customer"]!.DeepClone() }, "store_" + effectKey);
            var item = result["license"];
            Require(status == 201 && result["success"]?.GetValue<bool>() == true && item is JsonObject && Regex.IsMatch(Text(item["id"]), @"\Alic_[A-Za-z0-9_-]{1,100}\z") && Text(item["license_key"]).Length is >= 12 and <= 100 && Text(item["environment"]) == environment, "ISSUANCE_RESULT_REQUIRES_REVIEW");
            return (Text(item!["id"]), Text(item["license_key"]), result["request_id"]?.GetValue<string>());
        }
        var licenseId = StringValue(order, "license_id") ?? throw new Refusal("ISSUE_FIRST"); var route = "/licenses/" + Uri.EscapeDataString(licenseId);
        var (code, current) = await Api(HttpMethod.Get, route); var license = current["license"];
        Require(code == 200 && current["success"]?.GetValue<bool>() == true && license is JsonObject && Text(license["id"]) == licenseId && Text(license["environment"]) == environment && JsonNode.DeepEquals(license["status"], command["expected_status"]) && JsonNode.DeepEquals(license["expires_at"], command["expected_expires_at"]), "LICENSE_CHANGED_RECONCILE_FIRST");
        if (action == "record_cancellation") return (licenseId, null, current["request_id"]?.GetValue<string>());
        var body = action == "renew" ? new JsonObject { ["days"] = config["offers"]![Text(command["offer_id"])]!["renewal_days"]!.DeepClone() } : new JsonObject { ["reason"] = "Verified store fulfillment decision" };
        var (changedCode, changed) = await Api(HttpMethod.Post, route + "/" + action, body);
        var expected = action switch { "suspend" => "suspended", "restore" => "active", "revoke" => "revoked", "renew" => Text(license!["status"]) == "expired" ? "active" : Text(license["status"]), _ => throw new Refusal("UNSUPPORTED_ACTION") };
        Require(changedCode == 200 && changed["success"]?.GetValue<bool>() == true && Text(changed["license"]?["id"]) == licenseId && Text(changed["license"]?["status"]) == expected && (action != "renew" || changed["license"]?["expires_at"]?.GetValue<string>() is not null), "MUTATION_RESULT_REQUIRES_REVIEW");
        return (licenseId, null, changed["request_id"]?.GetValue<string>());
    }

    public async Task<object> Work()
    {
        var lockPath = Path.Combine(directory, "worker.lock"); Require(new FileInfo(lockPath).LinkTarget is null, "INVALID_LOCK_FILE");
        using var workerLock = new FileStream(lockPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);
        Require(flock(workerLock.SafeFileHandle.DangerousGetHandle().ToInt32(), 6) == 0, "WORKER_BUSY");
        var processed = 0;
        try
        {
            while (processed < 25)
            {
                var claimed = Transaction(() =>
                {
                    var effect = 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 is null) return null;
                    var order = One("SELECT * FROM orders WHERE order_key=@p0", effect["order_key"])!;
                    Run("UPDATE effects SET state='IN_FLIGHT' WHERE effect_key=@p0", effect["effect_key"]); Run("UPDATE orders SET held=1 WHERE order_key=@p0", effect["order_key"]);
                    return new[] { effect, order };
                });
                if (claimed is null) break;
                var effect = claimed[0];
                try
                {
                    var result = await Perform((JsonObject)JsonNode.Parse((string)effect["command_json"]!)!, claimed[1], (string)effect["effect_key"]!);
                    Require(result.RequestId is null || result.RequestId.Length <= 190, "UNEXPECTED_REQUEST_ID");
                    Transaction(() =>
                    {
                        if (result.Key is not null)
                        {
                            var nonce = RandomNumberGenerator.GetBytes(12); var plain = Utf8.GetBytes(result.Key); var cipher = new byte[plain.Length]; var tag = new byte[16];
                            using var aes = new AesGcm(outboxKey, 16); aes.Encrypt(nonce, plain, cipher, tag, Utf8.GetBytes((string)effect["order_key"]!));
                            Run("INSERT INTO outbox(order_key,license_id,cipher,created_at) VALUES(@p0,@p1,@p2,@p3)", effect["order_key"], result.Id, Convert.ToBase64String(nonce.Concat(cipher).Concat(tag).ToArray()), Now());
                            CryptographicOperations.ZeroMemory(plain);
                        }
                        Run("UPDATE effects SET state='DONE',request_id=@p0 WHERE effect_key=@p1", result.RequestId, effect["effect_key"]);
                        Run("UPDATE orders SET license_id=@p0,last_sequence=@p1,held=0 WHERE order_key=@p2", result.Id, effect["sequence"], effect["order_key"]);
                        return true;
                    });
                }
                catch { Run("UPDATE effects SET state='REVIEW',error_code='RECONCILE_BEFORE_RETRY' WHERE effect_key=@p0", effect["effect_key"]); }
                processed++; await Task.Delay(1100);
            }
        }
        finally { flock(workerLock.SafeFileHandle.DangerousGetHandle().ToInt32(), 8); }
        return new { processed, states = Inspect() };
    }

    public Dictionary<string, long> Inspect()
    {
        using var command = Command("SELECT state,COUNT(*) AS count FROM effects GROUP BY state"); using var rows = command.ExecuteReader();
        var result = new Dictionary<string, long>(); while (rows.Read()) result[rows.GetString(0)] = rows.GetInt64(1); return result;
    }
    public object Export(string order, string target)
    {
        Require(IsId(order), "INVALID_ORDER_ID"); var orderKey = Identity(Text(config["source"]), Text(config["seller_id"]), environment, order);
        var item = One("SELECT b.*,o.email FROM outbox b JOIN orders o ON o.order_key=b.order_key WHERE b.order_key=@p0", orderKey) ?? throw new Refusal("NO_SAVED_KEY");
        Require(Path.IsPathFullyQualified(target), "PRIVATE_EXPORT_DIRECTORY_REQUIRED"); var parent = Path.GetDirectoryName(target)!;
        Require(Directory.Exists(parent) && ((int)File.GetUnixFileMode(parent) & 0x3F) == 0, "PRIVATE_EXPORT_DIRECTORY_REQUIRED");
        var raw = Convert.FromBase64String((string)item["cipher"]!); Require(raw.Length >= 29, "INVALID_OUTBOX"); var plain = new byte[raw.Length - 28];
        using var aes = new AesGcm(outboxKey, 16); aes.Decrypt(raw.AsSpan(0, 12), raw.AsSpan(12, raw.Length - 28), raw.AsSpan(raw.Length - 16), plain, Utf8.GetBytes(orderKey));
        using var stream = new FileStream(target, FileMode.CreateNew, FileAccess.Write, FileShare.None); File.SetUnixFileMode(target, UnixFileMode.UserRead | UnixFileMode.UserWrite);
        var output = JsonSerializer.SerializeToUtf8Bytes(new { license_id = item["license_id"], license_key = Utf8.GetString(plain), email = item["email"] }, JsonOptions);
        stream.Write(output); stream.Flush(true); CryptographicOperations.ZeroMemory(plain);
        return new { exported = true, contains_secret = true };
    }

    public async Task Serve()
    {
        var builder = WebApplication.CreateSlimBuilder(); builder.Logging.ClearProviders();
        builder.WebHost.UseUrls("http://127.0.0.1:" + (Env("KDP_FULFILLMENT_PORT") is { Length: > 0 } port ? port : "8112"));
        builder.WebHost.ConfigureKestrel(options => { options.AddServerHeader = false; options.Limits.MaxRequestBodySize = Limit; options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(5); });
        var server = builder.Build();
        server.Run(async context =>
        {
            var request = context.Request; int status = 202; object result = new { accepted = false };
            try
            {
                if (request.Method != "POST" || request.Path != "/store-event" || request.QueryString.HasValue) status = 404;
                else if (request.Headers.ContainsKey("Transfer-Encoding") || request.ContentLength is null) status = 400;
                else if (request.ContentLength > Limit) status = 413;
                else if (request.Headers["X-Fulfillment-Signature"].Count != 1) status = 401;
                else if (request.ContentType?.Split(';')[0].Trim().ToLowerInvariant() != "application/json") status = 415;
                else
                {
                    using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); using var buffer = new MemoryStream();
                    await request.Body.CopyToAsync(buffer, timeout.Token);
                    Require(buffer.Length == request.ContentLength, "INVALID_BODY_LENGTH");
                    lock (receiverLock) result = Accept(buffer.ToArray(), request.Headers["X-Fulfillment-Signature"].ToString());
                }
            }
            catch (Refusal error) { status = error.Message == "INVALID_SIGNATURE" ? 401 : 409; }
            catch (JsonException) { status = 400; }
            catch (DecoderFallbackException) { status = 400; }
            catch (BadHttpRequestException error) { status = error.StatusCode; }
            catch { status = 503; }
            context.Response.StatusCode = status; context.Response.ContentType = "application/json";
            context.Response.Headers.CacheControl = "no-store"; context.Response.Headers.XContentTypeOptions = "nosniff";
            await context.Response.WriteAsync(JsonSerializer.Serialize(result, JsonOptions));
        });
        Console.WriteLine("{\"listening\":true,\"loopback_only\":true}");
        await server.RunAsync();
    }
    public void Dispose() { http.Dispose(); db.Dispose(); }
}

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, 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

SymptomCauseFix
Safe configuration errorMissing/mismatched private mapping, key, permissions or runtime dependencyVerify private environment and required modules; never publish raw secrets to debug
HTTP 401Wrong signature, timestamp, changed bytes or duplicate signature headerSign exact bytes with the separate command secret and verify the clock
HTTP 409Mapping/merchant/effect/sequence conflictReconcile trusted adapter state; do not invent a different effect ID
No work processedAlready complete, sequence gap, held order or busy workerInspect saved state and authorized history before any write
Export refusedWrong order/key, unsafe target directory or file already existsVerify recipient/order and use a new private owner-only target file

Next steps

Review the parent security checklist, then test your real provider-specific adapter separately in its test environment.

Was this page helpful?

Choose an answer

Do not include personal data, license keys or credentials. Retained for up to 30 days. This is not a support inbox.

Search documentation

Arrow keys to move, Enter to open, Escape to close. Search stays in your browser.

Cookie settings