<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Wilson Kemp</title>
    <description>The latest articles on DEV Community by Daniel Wilson Kemp (@daniel_wilsonkemp_62b88a).</description>
    <link>https://dev.to/daniel_wilsonkemp_62b88a</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4043109%2F01a2baab-c124-4624-b91f-00bd17e00346.png</url>
      <title>DEV Community: Daniel Wilson Kemp</title>
      <link>https://dev.to/daniel_wilsonkemp_62b88a</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/daniel_wilsonkemp_62b88a"/>
    <language>en</language>
    <item>
      <title>PAX A920 in 2026: What End-of-Life Means for Merchants</title>
      <dc:creator>Daniel Wilson Kemp</dc:creator>
      <pubDate>Mon, 03 Aug 2026 10:24:07 +0000</pubDate>
      <link>https://dev.to/daniel_wilsonkemp_62b88a/pax-a920-in-2026-what-end-of-life-means-for-merchants-3p6d</link>
      <guid>https://dev.to/daniel_wilsonkemp_62b88a/pax-a920-in-2026-what-end-of-life-means-for-merchants-3p6d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gehpqu8j5epi098aopi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5gehpqu8j5epi098aopi.png" alt="Lifted Pay on the PAX A920 family, PAXSTORE approved" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PAX A920 is still installed in a lot of merchant environments, but the buying decision changed in 2026.&lt;/p&gt;

&lt;p&gt;PAX's current North American product page labels the original model &lt;strong&gt;A920 (EOL)&lt;/strong&gt;. That is an important procurement signal. It is not, by itself, proof that every installed A920 must be unplugged today.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Short answer:&lt;/strong&gt; do not order the original A920 for a new deployment without written confirmation from the organization supporting the complete payment configuration. An existing A920 can remain usable only while its hardware, security support, payment application, processor configuration, and merchant deployment are all still supported.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the distinction merchants and developers need to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  End-of-life is a lifecycle signal, not a transaction response
&lt;/h2&gt;

&lt;p&gt;A payment terminal does not become unusable merely because a product page changes. The fielded system has several independent support boundaries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Boundary&lt;/th&gt;
&lt;th&gt;The question to ask&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware&lt;/td&gt;
&lt;td&gt;Can the device, battery, printer, charger, and radios still be supported?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firmware and security&lt;/td&gt;
&lt;td&gt;Is the installed firmware still accepted under the provider's security and compliance program?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment application&lt;/td&gt;
&lt;td&gt;Is the exact app build approved and assigned to this model?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processing configuration&lt;/td&gt;
&lt;td&gt;Does the processor or acquirer still support this terminal, application, host, and merchant setup?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merchant operations&lt;/td&gt;
&lt;td&gt;Do sales, receipts, reversals, refunds, settlement, and support escalation still work as designed?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;PAX itself still publishes the &lt;a href="https://www.pax.us/product/a920/" rel="noopener noreferrer"&gt;original A920 product page&lt;/a&gt; and an &lt;a href="https://www.pax.us/support/documents/a920-quick-setup-guide/" rel="noopener noreferrer"&gt;A920 quick setup guide&lt;/a&gt;. The product page's title currently carries the EOL label. Treat that label as a reason to verify the whole stack, not as permission to guess in either direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  A920, A920 Pro, or A920 Max?
&lt;/h2&gt;

&lt;p&gt;PAX's own &lt;a href="https://www.pax.us/compare-a920-models/" rel="noopener noreferrer"&gt;A920 family comparison&lt;/a&gt; describes three different positions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A920:&lt;/strong&gt; the compact original model for basic mobile or countertop acceptance. PAX currently marks this model EOL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A920 Pro:&lt;/strong&gt; the performance step up, with a larger display and newer hardware. PAX lists Wi-Fi, LTE, Bluetooth, an integrated printer, camera capability, and PAXSTORE access on its &lt;a href="https://www.pax.us/product/a920-pro-pci-5/" rel="noopener noreferrer"&gt;A920 Pro PCI 5 page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A920 Max:&lt;/strong&gt; the premium member of the line for environments that need more display, battery, and processing headroom.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PAX also announced a newer &lt;a href="https://www.pax.us/about/press-room/the-next-generation-a920pro-pci-7/" rel="noopener noreferrer"&gt;A920Pro PCI 7&lt;/a&gt; in April 2026. PAX describes Android 14, PCI PTS 7, a 6.56-inch main display, a secondary customer-facing display, 4G LTE, Wi-Fi, Bluetooth, eSIM support, an integrated scanner, and a 6000mAh battery.&lt;/p&gt;

&lt;p&gt;Those specifications matter, but they do not choose a payment terminal for you. The correct model is the one supported by the payment application, processing configuration, merchant category, connectivity plan, and operating workflow being deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  When an installed A920 can reasonably stay in service
&lt;/h2&gt;

&lt;p&gt;Keep an existing device only when the responsible provider can confirm all of the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The serial number remains assigned to the correct merchant and location.&lt;/li&gt;
&lt;li&gt;The device firmware and security posture remain supported.&lt;/li&gt;
&lt;li&gt;The required payment and business applications still receive managed updates.&lt;/li&gt;
&lt;li&gt;The processor configuration is still supported for that merchant account.&lt;/li&gt;
&lt;li&gt;Chip, contactless, swipe fallback, receipt, reversal, refund, and settlement workflows have been safely validated.&lt;/li&gt;
&lt;li&gt;Wi-Fi or cellular connectivity is stable in the real operating environment.&lt;/li&gt;
&lt;li&gt;There is a documented support and replacement path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A device that turns on is not necessarily a supported payment deployment. Conversely, an EOL procurement label does not automatically erase a working, supported installed base.&lt;/p&gt;

&lt;h2&gt;
  
  
  PAXSTORE approval is necessary evidence, not the entire deployment
&lt;/h2&gt;

&lt;p&gt;PAX describes &lt;a href="https://www.pax.us/paxstore/" rel="noopener noreferrer"&gt;PAXSTORE&lt;/a&gt; as its device and application management ecosystem. That distribution layer is one of the reasons Android payment terminals can carry business applications alongside the configured payment stack.&lt;/p&gt;

&lt;p&gt;But three statements are different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the application build is approved for distribution;&lt;/li&gt;
&lt;li&gt;the application is assigned to a particular device;&lt;/li&gt;
&lt;li&gt;the merchant's complete production workflow is ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not collapse them into one claim.&lt;/p&gt;

&lt;p&gt;For example, Lifted Payments currently offers two PAX PayDroid applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://liftedpayments.com/LiftedPay" rel="noopener noreferrer"&gt;Lifted Pay&lt;/a&gt; is the standalone merchant-operated application for qualified card-present deployments.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://liftedpayments.com/lifted-connect/" rel="noopener noreferrer"&gt;Lifted POS Connect&lt;/a&gt; is the payment companion that receives a sale from LiftedPOS, presents the supported tender flow on the terminal, and returns the result to the register.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PAXSTORE approved Lifted Pay version 0.0.5 and Lifted POS Connect version 0.2.9 on July 27, 2026. That is evidence about the named application builds and their distribution status. Merchant rollout still requires supported hardware, processing configuration, boarding, and validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical first-boot and handoff checklist
&lt;/h2&gt;

&lt;p&gt;The official PAX quick guide covers the physical basics: inventory the terminal, power supply, and paper roll; prepare the battery; and hold the power key for roughly three to five seconds.&lt;/p&gt;

&lt;p&gt;For a production handoff, extend that checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Record the model, serial number, location, owner, and intended payment application.&lt;/li&gt;
&lt;li&gt;Charge the device fully and inspect the battery, printer, charging contacts, and paper path.&lt;/li&gt;
&lt;li&gt;Join the approved Wi-Fi network or validate the managed cellular plan.&lt;/li&gt;
&lt;li&gt;Confirm the correct PAXSTORE assignment and let all managed updates finish.&lt;/li&gt;
&lt;li&gt;Verify the merchant identity and processing configuration before taking a payment.&lt;/li&gt;
&lt;li&gt;Run a controlled card-present test through the real workflow.&lt;/li&gt;
&lt;li&gt;Confirm the result appears in the expected reporting and receipt surfaces.&lt;/li&gt;
&lt;li&gt;Safely test the supported reversal or refund path.&lt;/li&gt;
&lt;li&gt;Document who owns terminal, app, processing, and connectivity support.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last item matters. A merchant should never have to discover during an outage that four vendors each believe someone else owns the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replace the device when support becomes ambiguous
&lt;/h2&gt;

&lt;p&gt;Replacement is the safer choice when any of these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the provider will not confirm continuing model or firmware support;&lt;/li&gt;
&lt;li&gt;managed updates no longer arrive;&lt;/li&gt;
&lt;li&gt;battery, printer, or connectivity failures are recurring;&lt;/li&gt;
&lt;li&gt;the payment application is not approved or supported on the installed build;&lt;/li&gt;
&lt;li&gt;the merchant cannot safely test reversals, refunds, or reporting;&lt;/li&gt;
&lt;li&gt;security or compliance policy requires a newer device generation;&lt;/li&gt;
&lt;li&gt;downtime and support cost now exceed the value of keeping the device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For new purchases, get the complete supported configuration in writing before ordering hardware. The terminal model alone is not the solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The merchant decision is bigger than the terminal
&lt;/h2&gt;

&lt;p&gt;A PAX device is one layer of a payment system. The merchant account, processing configuration, terminal application, POS or gateway workflow, reporting, refunds, connectivity, and support ownership all have to agree.&lt;/p&gt;

&lt;p&gt;The full &lt;a href="https://liftedpayments.com/pax-a920-setup-and-app-guide" rel="noopener noreferrer"&gt;PAX A920 setup and application guide&lt;/a&gt; maintains the current model comparison, setup steps, PAXSTORE explanation, troubleshooting path, and merchant-deployment checklist in one place.&lt;/p&gt;

&lt;p&gt;If the goal is a new processing relationship rather than hardware-only research, start with the &lt;a href="https://liftedholdings.com/apply" rel="noopener noreferrer"&gt;merchant application&lt;/a&gt; so the business, risk profile, hardware, and software path can be evaluated together.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Source note:&lt;/strong&gt; product names and specifications above are based on the linked PAX Technology North America pages reviewed August 3, 2026. Availability, certification, firmware, and provider support can change. Confirm the exact configuration before purchasing or fielding a device.&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>android</category>
      <category>security</category>
      <category>iot</category>
    </item>
    <item>
      <title>A Processor-Neutral JSON Schema for Auditing Payment Processing Statements</title>
      <dc:creator>Daniel Wilson Kemp</dc:creator>
      <pubDate>Sun, 02 Aug 2026 16:32:52 +0000</pubDate>
      <link>https://dev.to/daniel_wilsonkemp_62b88a/a-processor-neutral-json-schema-for-auditing-payment-processing-statements-2cg2</link>
      <guid>https://dev.to/daniel_wilsonkemp_62b88a/a-processor-neutral-json-schema-for-auditing-payment-processing-statements-2cg2</guid>
      <description>&lt;p&gt;A Processor-Neutral JSON Schema for Auditing Payment Processing Statements&lt;/p&gt;

&lt;p&gt;Merchant processing statements are built for billing, not comparison. Two statements can describe the same monthly card volume with different labels, subtotals, credits, and pricing structures. That makes the basic question—“what did accepting cards actually cost?”—harder to answer than it should be.&lt;/p&gt;

&lt;p&gt;We published version 1.1.3 of the &lt;strong&gt;Lifted Payments Payment Statement Audit Model&lt;/strong&gt; to make that calculation explicit, reproducible, and independently verifiable. The release includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a spreadsheet-ready CSV template;&lt;/li&gt;
&lt;li&gt;a JSON Schema Draft 2020-12 contract;&lt;/li&gt;
&lt;li&gt;a Decimal-safe companion validator;&lt;/li&gt;
&lt;li&gt;exact methodology and field definitions;&lt;/li&gt;
&lt;li&gt;two valid and fourteen deliberately invalid synthetic test vectors;&lt;/li&gt;
&lt;li&gt;a deterministic validation report and SHA-256 inventory; and&lt;/li&gt;
&lt;li&gt;a fail-closed publication gate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The maintained guide and downloads live at &lt;a href="https://liftedpayments.com/payment-processing-statement-audit/" rel="noopener noreferrer"&gt;liftedpayments.com/payment-processing-statement-audit&lt;/a&gt;. The exact release is archived as &lt;a href="https://doi.org/10.5281/zenodo.21764642" rel="noopener noreferrer"&gt;doi:10.5281/zenodo.21764642&lt;/a&gt; and published from &lt;a href="https://github.com/Lifted-Holdings/payment-processing-resources/releases/tag/v1.1.3" rel="noopener noreferrer"&gt;GitHub v1.1.3&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The canonical release archive has SHA-256 digest &lt;code&gt;b1d398ada2bb456de0fffc5278b68a48554ffd5d80232d654416102ad87d5897&lt;/code&gt;. That digest is the simplest way to confirm that a downloaded copy is the release described here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one declared calculation basis
&lt;/h2&gt;

&lt;p&gt;Version 1.1.3 uses gross settled purchase volume and net processing fees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gross_processing_fees = exact sum(fee_groups.amount)
total_processing_fees = gross_processing_fees - statement_credits
effective_rate = round_half_up(total_processing_fees / card_volume, 6)
average_ticket = round_half_up(card_volume / transaction_count, 2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the synthetic example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$2,864.75 / $125,000.00 = 0.022918 = 2.2918%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON value is &lt;code&gt;0.022918&lt;/code&gt;, not &lt;code&gt;2.2918&lt;/code&gt;. Multiply by 100 only for display.&lt;/p&gt;

&lt;p&gt;The denominator is &lt;strong&gt;gross settled purchase volume&lt;/strong&gt; with its matching settled purchase count. It excludes declines, authorization-only events, voids, refunds, chargebacks, reserves, and funding adjustments. The numerator is &lt;strong&gt;net processing fees&lt;/strong&gt; after gross fee groups reconcile exactly and processing-fee credits are reported separately.&lt;/p&gt;

&lt;p&gt;That basis matters. Dividing fees by deposits, net sales after refunds, or authorization volume can produce a plausible-looking percentage that is not comparable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The record makes its assumptions machine-readable
&lt;/h2&gt;

&lt;p&gt;A shortened v1.1.3 record looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"calculation_basis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gross_settled_purchase_volume_and_net_processing_fees"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statement_period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-06-30"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"card_volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;125000.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transaction_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1860&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gross_processing_fees"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2864.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statement_credits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_processing_fees"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2864.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"effective_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.022918&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"average_ticket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;67.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pricing_model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"interchange_plus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fee_groups"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The published example includes the complete fee-group array. Categories are unique, and their exact cent sum must equal &lt;code&gt;gross_processing_fees&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pricing_model&lt;/code&gt; uses a bounded set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interchange_plus
flat_rate
tiered
subscription
dual_pricing
unknown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;unknown&lt;/code&gt; is intentional. A parser should preserve uncertainty rather than infer a pricing model from one line item.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normalize labels into stable fee groups
&lt;/h2&gt;

&lt;p&gt;Statement labels vary, but the economic roles are more stable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;interchange&lt;/code&gt; — issuer/interchange charges;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assessments&lt;/code&gt; — network assessment, access, and brand charges;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;processor_markup&lt;/code&gt; — percentage, basis-point, per-item, or service markup;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;authorization&lt;/code&gt; — authorization, capture, AVS, gateway, and batch items;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;monthly&lt;/code&gt; — account, statement, minimum, and recurring platform charges;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pci&lt;/code&gt; — compliance-program or non-validation fees;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;equipment&lt;/code&gt; — terminal rental, device service, or connectivity charges;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;chargebacks&lt;/code&gt; — dispute-administration fees, not transaction principal; and&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;other&lt;/code&gt; — ambiguous processing-related charges, with a note.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model does not assume every fee is avoidable. It makes the statement explainable. If a processing charge cannot be classified confidently, put it in &lt;code&gt;other&lt;/code&gt; and document why; do not omit it to make a rate look better.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON Schema is necessary, but not sufficient
&lt;/h2&gt;

&lt;p&gt;The schema handles types, required fields, enums, bounds, and unknown-field rejection. Cross-field accounting rules need a semantic validator.&lt;/p&gt;

&lt;p&gt;Install the pinned dependencies and run both a record and the public corpus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pip install -r requirements-validation.txt
python tools/validate_audit.py examples/payment-statement-audit-example.json
python tools/validate_audit.py --corpus
python tools/publication_gate.py --mode package
python tools/publication_gate.py --mode candidate
python tools/publication_gate.py --mode published
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validator loads JSON numbers directly as &lt;code&gt;Decimal&lt;/code&gt;. It also rejects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate JSON keys and non-finite numbers;&lt;/li&gt;
&lt;li&gt;invalid or reversed dates and periods longer than 62 days;&lt;/li&gt;
&lt;li&gt;fractional cents and over-precise rates;&lt;/li&gt;
&lt;li&gt;fee totals, credits, effective rates, or average tickets that do not reconcile;&lt;/li&gt;
&lt;li&gt;ambiguous zero-volume and zero-count combinations;&lt;/li&gt;
&lt;li&gt;duplicate fee categories and unknown fields;&lt;/li&gt;
&lt;li&gt;excessive input size or nesting depth; and&lt;/li&gt;
&lt;li&gt;likely payment, banking, identity, email, or credential data in notes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validation errors return a rule code, JSON Pointer path, and fixed message without echoing the submitted value.&lt;/p&gt;

&lt;p&gt;The exact merge worktree passed 81 automated tests. A fresh copy unpacked from the published ZIP passed the same 81-test suite, with one Git-history-only immutability regression skipped as designed. Package mode passed 14/14 internal checks, the clean tagged candidate passed 15/15 readiness checks, and published mode passed 16/16 only after GitHub, Zenodo, DOI resolution, Hugging Face, and Software Heritage matched the reviewed commit and archive bytes. The deterministic report accepts two valid synthetic records and rejects fourteen adversarial records for their expected rules.&lt;/p&gt;

&lt;p&gt;Those results demonstrate the declared contract, package integrity, rejection behavior, repository provenance, and public artifact identity. They do not prove that an arbitrary source statement was transcribed correctly, and they are not a security guarantee.&lt;/p&gt;

&lt;p&gt;Version 1.1.3 keeps the 1.1.0 record contract unchanged while separating portable package validity from candidate readiness and published attestation. Before candidate Python can run, the gate checks the complete manifest, canonical paths, bounded sizes, UTF-8 and LF portability, SHA-256 coverage, credential signatures, release identity, clean committed tree, authoritative origin, complete remote tag inventory, exact tag alignment, and monotonic version. The public attestor is bounded and non-executing; it rejects unsafe redirects, oversized responses, archive traversal, case-fold aliases, symlinks, undeclared members, digest drift, and mismatched public identities.&lt;/p&gt;

&lt;p&gt;The gate is not an accounting opinion, legal review, interchange-pricing guarantee, or malware sandbox. Automated screening cannot prove arbitrary prose contains no confidential information, so human review remains mandatory before public release.&lt;/p&gt;

&lt;p&gt;The release was also exercised against 1,000 deterministic hostile JSON-native and direct-value records. Every case returned structured validation results rather than crashing the validator or leaking submitted values into error messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the public-data boundary strict
&lt;/h2&gt;

&lt;p&gt;This model needs statement-level aggregates. It has no field for card numbers, cardholder identity, bank details, credentials, or merchant-owner information.&lt;/p&gt;

&lt;p&gt;Never put these into a public repository, prompt, issue, or dataset mirror:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full or partial card numbers, expiration dates, or security codes;&lt;/li&gt;
&lt;li&gt;track, PIN, or PIN-block data;&lt;/li&gt;
&lt;li&gt;bank account or routing numbers;&lt;/li&gt;
&lt;li&gt;passwords, API keys, tokens, or authentication values;&lt;/li&gt;
&lt;li&gt;tax IDs, government identifiers, or owner Social Security numbers;&lt;/li&gt;
&lt;li&gt;merchant-identifying notes; or&lt;/li&gt;
&lt;li&gt;an unredacted real statement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automated screening reduces accidental disclosure. It cannot prove arbitrary prose is safe, so a human still has to review every public record.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the model cannot tell you
&lt;/h2&gt;

&lt;p&gt;An effective rate is an all-in normalization of the included statement costs. It can support consistent month-to-month or provider-to-provider comparisons when records use the same basis.&lt;/p&gt;

&lt;p&gt;It cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prove the source statement was transcribed correctly;&lt;/li&gt;
&lt;li&gt;decide whether a fee is contractually permitted;&lt;/li&gt;
&lt;li&gt;determine whether interchange was qualified correctly;&lt;/li&gt;
&lt;li&gt;predict a future statement;&lt;/li&gt;
&lt;li&gt;replace legal, tax, or accounting advice; or&lt;/li&gt;
&lt;li&gt;guarantee savings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Card mix, ticket size, acceptance channel, rewards mix, disputes, seasonality, and statement timing can move the result without a pricing change. Present the rate with the underlying dollars, volume, count, fee groups, and period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cite or reuse the exact release
&lt;/h2&gt;

&lt;p&gt;The model is licensed under CC BY 4.0. The requested citation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lifted Payments. (2026). &lt;em&gt;Lifted Payments Payment Statement Audit Model&lt;/em&gt; (Version 1.1.3). Zenodo. &lt;a href="https://doi.org/10.5281/zenodo.21764642" rel="noopener noreferrer"&gt;https://doi.org/10.5281/zenodo.21764642&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Public distribution surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://liftedpayments.com/payment-processing-statement-audit/" rel="noopener noreferrer"&gt;Canonical methodology and downloads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Lifted-Holdings/payment-processing-resources/releases/tag/v1.1.3" rel="noopener noreferrer"&gt;GitHub v1.1.3 release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zenodo.org/records/21764642" rel="noopener noreferrer"&gt;Zenodo version record&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/datasets/Liftedholdings/payment-statement-audit-model/tree/a001235f4195fc534660100e01db41d10712eaac" rel="noopener noreferrer"&gt;Exact Hugging Face v1.1.3 commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://archive.softwareheritage.org/swh:1:snp:52b9172c2533e1686d0a1acf9f541029262f4d3e/" rel="noopener noreferrer"&gt;Software Heritage snapshot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GitHub is the versioned source, Zenodo is the persistent DOI archive, and Software Heritage preserves the exact source history. The Hugging Face catalog copy exposes the same v1.1.3 ZIP; an anonymous download matched the canonical archive digest above.&lt;/p&gt;

&lt;p&gt;For a quick manual check, the &lt;a href="https://liftedpayments.com/effective-rate-calculator" rel="noopener noreferrer"&gt;free effective-rate calculator&lt;/a&gt; performs the core math in the browser without transmitting the values entered.&lt;/p&gt;

&lt;p&gt;If you want a payments team to review a private statement and design processing technology around how the business actually operates, &lt;a href="https://liftedholdings.com/apply" rel="noopener noreferrer"&gt;start the Lifted Payments merchant application&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>fintech</category>
      <category>opensource</category>
      <category>json</category>
      <category>data</category>
    </item>
    <item>
      <title>Level 2/3 interchange data: how B2B card payments qualify for lower rates</title>
      <dc:creator>Daniel Wilson Kemp</dc:creator>
      <pubDate>Mon, 27 Jul 2026 14:06:50 +0000</pubDate>
      <link>https://dev.to/daniel_wilsonkemp_62b88a/level-23-interchange-data-how-b2b-card-payments-qualify-for-lower-rates-3l68</link>
      <guid>https://dev.to/daniel_wilsonkemp_62b88a/level-23-interchange-data-how-b2b-card-payments-qualify-for-lower-rates-3l68</guid>
      <description>&lt;p&gt;If you build or run software that takes B2B card payments, there's a lever most merchants never pull: &lt;strong&gt;Level 2 and Level 3 interchange data.&lt;/strong&gt; Send the card networks more structured detail about a commercial-card transaction, and that transaction can settle at a materially lower interchange rate. Here's how it actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interchange, briefly
&lt;/h2&gt;

&lt;p&gt;Every card transaction carries an &lt;strong&gt;interchange fee&lt;/strong&gt; set by the card networks (Visa, Mastercard, etc.) and paid to the card-issuing bank. It's the largest, effectively non-negotiable component of what a merchant pays to accept a card. Interchange isn't one number — it's hundreds of rate categories, and which one a transaction lands in depends on the card type, how it's accepted, and &lt;strong&gt;the data submitted with it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Commercial cards — corporate, purchasing, business, and government cards — have their own interchange categories, and those categories reward richer data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Level 1 vs Level 2 vs Level 3
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Level 1&lt;/strong&gt; — the default. Just the basics: card number, amount, date. Every transaction has this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 2&lt;/strong&gt; — adds a few fields: &lt;strong&gt;sales tax amount&lt;/strong&gt;, a &lt;strong&gt;customer/PO reference number&lt;/strong&gt;, and tax status. Valid Level 2 data can qualify a commercial-card transaction for a lower interchange tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 3&lt;/strong&gt; — adds &lt;strong&gt;full line-item detail&lt;/strong&gt;: per-item description, product code, quantity, unit of measure, unit price, line-item tax, freight/duty, and more. This is the richest tier and the one purchasing and government cards are built around.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catch: the data has to be &lt;strong&gt;valid&lt;/strong&gt;. Networks validate it. A placeholder tax amount or a bogus product code won't qualify — and in 2026 the bar got higher (Visa folded its old standalone Level 2 incentive into the Commercial Enhanced Data Program, which requires accurate enhanced data plus a small participation fee).&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example
&lt;/h2&gt;

&lt;p&gt;Say a distributor charges a business customer $5,000 on a corporate Visa card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Level 1 (basics only):        standard commercial rate
Level 2 (+ tax, PO number):   reduced commercial tier
Level 3 (+ full line items):  lowest commercial tier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact basis-point difference depends on the card and network rules, so I won't quote a fixed number — anyone who &lt;em&gt;guarantees&lt;/em&gt; you a specific rate is guessing. But on large-ticket B2B volume, the gap between Level 1 and Level 3 on commercial cards is meaningful and compounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually send it
&lt;/h2&gt;

&lt;p&gt;Three things have to line up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Your gateway/processor has to support it.&lt;/strong&gt; Many don't append Level 2/3 well, or make you pass every field by hand. Some gateways append it automatically when the card is commercial and the data is present.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need the data.&lt;/strong&gt; Level 2 is easy — you almost always have tax and an invoice/PO number. Level 3 means passing your line items through at charge time (your cart or invoice already has them).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The card has to be a commercial card.&lt;/strong&gt; Consumer cards don't qualify for these tiers, so there's nothing to optimize there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're wiring this up yourself, the practical pattern is: detect the commercial BIN, then attach the enhanced-data object (tax, PO, line items) to the authorization/capture request. If your gateway supports auto-append, you send the line items once and it handles qualification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters for B2B software
&lt;/h2&gt;

&lt;p&gt;If your product invoices other businesses — SaaS, wholesale, marketplaces, field service — a non-trivial share of your card volume is commercial cards. Getting Level 2/3 right is one of the few ways to lower effective processing cost &lt;strong&gt;without&lt;/strong&gt; changing your prices or your pricing model.&lt;/p&gt;

&lt;p&gt;For a deeper, vendor-neutral walkthrough — the specific fields for each level and how qualification is decided — I wrote it up here: &lt;strong&gt;&lt;a href="https://liftedpayments.com/level-2-3-processing" rel="noopener noreferrer"&gt;Level 2/3 processing: how commercial-card interchange optimization works&lt;/a&gt;&lt;/strong&gt;. There's also a companion piece on &lt;a href="https://liftedpayments.com/b2b-credit-card-processing" rel="noopener noreferrer"&gt;B2B credit card processing&lt;/a&gt; for the business-side view.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm the founder of &lt;a href="https://liftedpayments.com/" rel="noopener noreferrer"&gt;Lifted Payments&lt;/a&gt;, where we append Level 2/3 automatically on commercial cards, so I have skin in this game. But the mechanics above are card-network rules, not vendor magic — any processor that supports enhanced data can do this. If yours doesn't, that's worth a conversation with them.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>fintech</category>
      <category>software</category>
    </item>
    <item>
      <title>Lifted Sign: a self-hostable e-signature server that runs on nothing but SQLite</title>
      <dc:creator>Daniel Wilson Kemp</dc:creator>
      <pubDate>Thu, 23 Jul 2026 08:37:23 +0000</pubDate>
      <link>https://dev.to/daniel_wilsonkemp_62b88a/lifted-sign-a-self-hostable-e-signature-server-that-runs-on-nothing-but-sqlite-3898</link>
      <guid>https://dev.to/daniel_wilsonkemp_62b88a/lifted-sign-a-self-hostable-e-signature-server-that-runs-on-nothing-but-sqlite-3898</guid>
      <description>&lt;p&gt;Most e-signature tools send signatures. Very few let you &lt;em&gt;own&lt;/em&gt; the whole thing — the documents, the signers, and the audit trail — on your own hardware. We built &lt;strong&gt;Lifted Sign&lt;/strong&gt; to be the one you can self-host, and to be genuinely boring to run: it boots on nothing but SQLite.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/LiftedHoldings/lifted-sign" rel="noopener noreferrer"&gt;https://github.com/LiftedHoldings/lifted-sign&lt;/a&gt; (AGPL-3.0)&lt;/p&gt;

&lt;h2&gt;
  
  
  The pitch, honestly
&lt;/h2&gt;

&lt;p&gt;Upload a PDF, place fields, add signers, send single-use signing links. Each completed document is cryptographically sealed and ships with a &lt;strong&gt;Certificate of Completion&lt;/strong&gt; — the audit trail of signer identities, timestamps, IP addresses, and consent records that ESIGN and UETA actually call for.&lt;/p&gt;

&lt;p&gt;The difference is where all of that lives: your infrastructure, not a vendor's cloud. Data residency and air-gapped deploys are the default, not an enterprise tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQLite by default
&lt;/h2&gt;

&lt;p&gt;This is the part I'm happiest with. Lifted Sign boots with zero external services — one secret, and you're signing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SIGN_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 48&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 ghcr.io/liftedholdings/lifted-sign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8080&lt;/code&gt; and you have a working signing server. &lt;code&gt;SIGN_SECRET&lt;/code&gt; is the only required setting; every login session, signer-access cookie, and one-time code is keyed off it, so the server fails closed and loud if it's missing, too short, or a placeholder. Point it at Postgres with a single env var when you outgrow a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real PAdES certification, not a flattened image
&lt;/h2&gt;

&lt;p&gt;A lot of "e-signature" ends up being a signature image burned into a PDF. Lifted Sign gives completed PDFs a &lt;strong&gt;PKCS#7 / PAdES&lt;/strong&gt; digital signature that any PDF reader can verify and that breaks visibly if the file is altered. No signing certificate installed yet? It falls back to a tamper-evident AES-integrity seal, so a completed document is never left unsealed.&lt;/p&gt;

&lt;h2&gt;
  
  
  How signing works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prepare.&lt;/strong&gt; Drop in a PDF, place signature/date/text/checkbox fields by anchor, add signers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send.&lt;/strong&gt; Each signer gets a single-use link. They review, hit the ESIGN/UETA consent gate, and sign in the browser — no account required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Seal.&lt;/strong&gt; Once everyone signs, the PDF is sealed with a PAdES certification signature and a Certificate of Completion. Download both, or pull them via the API.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Developer-first
&lt;/h2&gt;

&lt;p&gt;There's a clean REST API under &lt;code&gt;/api/mysign/*&lt;/code&gt; for creating envelopes, adding signers, placing fields, sending, reminding, voiding, and downloading sealed PDFs and certificates. The in-app &lt;code&gt;/developers&lt;/code&gt; page documents the endpoints and serves an OpenAPI spec.&lt;/p&gt;

&lt;p&gt;Ready-to-use, dependency-free clients are vendored in the repo — a Python client (3.8+, standard library only) and a Node client (18+, built-in &lt;code&gt;fetch&lt;/code&gt;/&lt;code&gt;FormData&lt;/code&gt;). Both are &lt;strong&gt;MIT-licensed&lt;/strong&gt; even though the server is AGPL, so integrating against Lifted Sign never touches your application's licensing. Point either client's &lt;code&gt;base_url&lt;/code&gt; at your own install and drive it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build from source
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/LiftedHoldings/lifted-sign.git
&lt;span class="nb"&gt;cd &lt;/span&gt;lifted-sign
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import secrets; print('SIGN_SECRET=' + secrets.token_urlsafe(48))"&lt;/span&gt;  &lt;span class="c"&gt;# put this in .env&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; sign        &lt;span class="c"&gt;# or: docker compose up&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why AGPL, and why free
&lt;/h2&gt;

&lt;p&gt;Self-hosting is GA and &lt;strong&gt;free forever&lt;/strong&gt; under the AGPL — no seat limits, no volume policing, no "contact us." The license is the only agreement. Passwordless email magic-link sign-in works out of the box; SMTP, Postgres, Google/phone sign-in, and PAdES certificates are optional add-ons you switch on by env var as you grow.&lt;/p&gt;

&lt;p&gt;If you'd rather not run it, there's a managed instance in free open beta at &lt;a href="https://sign.liftedholdings.com" rel="noopener noreferrer"&gt;https://sign.liftedholdings.com&lt;/a&gt; — same software, TLS and backups handled — but the self-hosted path stays free regardless.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo + docs:&lt;/strong&gt; &lt;a href="https://github.com/LiftedHoldings/lifted-sign" rel="noopener noreferrer"&gt;https://github.com/LiftedHoldings/lifted-sign&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture write-up&lt;/strong&gt; (the ESIGN/UETA model, the three-runtime design, the persistence and security layers) lives in &lt;code&gt;docs/ARCHITECTURE.md&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built by&lt;/strong&gt; &lt;a href="https://liftedholdings.com" rel="noopener noreferrer"&gt;Lifted Holdings&lt;/a&gt;; see also &lt;a href="https://liftedpayments.com" rel="noopener noreferrer"&gt;Lifted Payments&lt;/a&gt; for interchange-plus merchant services with 3-D Secure card processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a debut open-source project, so contributors are what turn it into a community — there are &lt;code&gt;good first issue&lt;/code&gt; labels waiting. Bug reports, docs, tests, and code all welcome.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>security</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>Lifted ShipKit: open-source multi-carrier shipping labels with forced 3-D Secure</title>
      <dc:creator>Daniel Wilson Kemp</dc:creator>
      <pubDate>Thu, 23 Jul 2026 08:36:26 +0000</pubDate>
      <link>https://dev.to/daniel_wilsonkemp_62b88a/lifted-shipkit-open-source-multi-carrier-shipping-labels-with-forced-3-d-secure-5dd1</link>
      <guid>https://dev.to/daniel_wilsonkemp_62b88a/lifted-shipkit-open-source-multi-carrier-shipping-labels-with-forced-3-d-secure-5dd1</guid>
      <description>&lt;p&gt;Shipping labels are one of those features that look trivial until you actually build them. You need a carrier-rating API, a payment step, PCI scope for the card data, and — the part nobody warns you about — a fraud problem, because labels bought with stolen cards are a top chargeback category. Real goods, shipped fast, disputed weeks later.&lt;/p&gt;

&lt;p&gt;We open-sourced &lt;strong&gt;Lifted ShipKit&lt;/strong&gt; to collapse that whole stack into a few lines of code. It's MIT-licensed, self-hostable, and it ships with a drop-in checkout widget that forces 3-D Secure on every card charge.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/LiftedHoldings/lifted-shipkit" rel="noopener noreferrer"&gt;https://github.com/LiftedHoldings/lifted-shipkit&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually is
&lt;/h2&gt;

&lt;p&gt;ShipKit is a Kotlin/JVM backend plus a dependency-free browser widget. The backend wraps &lt;a href="https://www.easypost.com/" rel="noopener noreferrer"&gt;EasyPost&lt;/a&gt; for real multi-carrier shipping — address verification, live USPS/UPS/FedEx rate compare, SmartRates, label purchase, batch, scan forms, customs, and tracking webhooks — and adds a payment layer on top of it. The widget is a UMD global (&lt;code&gt;window.ShipKit&lt;/code&gt;), so it mounts in plain HTML, React, Vue, Svelte, or anything else with a &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag and no build step.&lt;/p&gt;

&lt;p&gt;The part I want to talk about is the payment layer, because that's the design decision that makes ShipKit different from "just call EasyPost yourself."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why forced 3-D Secure is the whole point
&lt;/h2&gt;

&lt;p&gt;Most teams that add shipping wire a rating API straight to a payment processor and then quietly own two things they didn't sign up for: the PCI scope for card data, and the chargeback liability when a fraudster buys a label with a stolen card.&lt;/p&gt;

&lt;p&gt;ShipKit forces &lt;strong&gt;3-D Secure&lt;/strong&gt; on every card charge. The issuer authenticates the buyer &lt;em&gt;before&lt;/em&gt; the label prints, which shifts fraud-and-chargeback liability off the merchant. Card data goes through hosted fields, so it stays out of your PCI scope. This isn't an enterprise upsell or a config flag you can forget to turn on — it's always on, by construction.&lt;/p&gt;

&lt;p&gt;For a product whose fraud profile is "real goods, shipped immediately, disputed later," making authentication non-optional is the correct default.&lt;/p&gt;

&lt;h2&gt;
  
  
  60-second try
&lt;/h2&gt;

&lt;p&gt;No account, no keys — the published Docker image comes up immediately (shipping and payment features return &lt;code&gt;503&lt;/code&gt; until you supply an EasyPost key and 3DS credentials):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;SHIPKIT_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8080 &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 ghcr.io/liftedholdings/lifted-shipkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drop it into your own checkout
&lt;/h2&gt;

&lt;p&gt;The headline use case is keeping your app and your checkout and adding ShipKit for the rate-compare -&amp;gt; pay -&amp;gt; print-label step. Mount a node, point it at your backend, wire a callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"ship"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/js/shipkit.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;ShipKit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ship&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pk_live_your_publishable_key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// publishable pk_… key — safe in the browser&lt;/span&gt;
    &lt;span class="na"&gt;onPurchase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;trackingCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;labelUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// hand the finished label back to your order flow&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keys come in two scopes: the browser widget uses a &lt;strong&gt;publishable&lt;/strong&gt; &lt;code&gt;pk_…&lt;/code&gt; key (the backend confines it to the customer flow), while your server calls use a &lt;strong&gt;secret&lt;/strong&gt; &lt;code&gt;sk_…&lt;/code&gt; key that never appears in client code. Every surface restyles with &lt;code&gt;--sk-*&lt;/code&gt; CSS variables — no &lt;code&gt;!important&lt;/code&gt;, no build step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-host it fully
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/LiftedHoldings/lifted-shipkit.git
&lt;span class="nb"&gt;cd &lt;/span&gt;shipkit
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env                       &lt;span class="c"&gt;# EASYPOST_API_KEY + your 3DS keys&lt;/span&gt;
./gradlew build                            &lt;span class="c"&gt;# Kotlin 2.0.21, JVM 17&lt;/span&gt;
./gradlew shipkitKeygen &lt;span class="nt"&gt;-Plabel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-store   &lt;span class="c"&gt;# mint an API key&lt;/span&gt;
./gradlew run                              &lt;span class="c"&gt;# API + widget on :8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every setting is read from the environment. Two optional, env-driven hooks let a platform operate a fleet of instances from a control plane — a bearer-guarded &lt;code&gt;POST /api/config/markup&lt;/code&gt; to update the shipping markup remotely, and a fire-and-forget &lt;code&gt;label.purchased&lt;/code&gt; webhook (carrier cost and buyer charge in integer cents) — both &lt;strong&gt;off by default&lt;/strong&gt; when their tokens are unset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shipping as a profit center
&lt;/h2&gt;

&lt;p&gt;One design choice worth calling out for anyone building a store: whoever owns the payments account owns the markup. ShipKit prices every label at the carrier rate &lt;strong&gt;plus your own percentage and fixed fee&lt;/strong&gt; (&lt;code&gt;percentage_markup&lt;/code&gt; + &lt;code&gt;fixed_fee_cents&lt;/code&gt;), applied server-side and shown at checkout. Self-host it and that margin is 100% yours on every label.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open source it
&lt;/h2&gt;

&lt;p&gt;Because the honest version of this tool is one you can read, fork, and run yourself. MIT license, dependency-free frontend, clean modular backend, no per-label SaaS fee when you self-host. If you'd rather not run infra, there's a managed tier and a 3-D Secure merchant-account tier — but the code, the widget, and self-hosting stay free.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://liftedholdings.com/shippingtool" rel="noopener noreferrer"&gt;https://liftedholdings.com/shippingtool&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repo + docs:&lt;/strong&gt; &lt;a href="https://github.com/LiftedHoldings/lifted-shipkit" rel="noopener noreferrer"&gt;https://github.com/LiftedHoldings/lifted-shipkit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The payments side&lt;/strong&gt; (interchange-plus merchant services with 3-D Secure, built by the same team): &lt;a href="https://liftedpayments.com" rel="noopener noreferrer"&gt;https://liftedpayments.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build something with it, or you want ShipKit woven into your own framework, we'd genuinely like to hear about it. PRs and issues welcome.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>kotlin</category>
      <category>payments</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
