Skip to main content

Building Plugins with coinsnap-core

coinsnap-core is a shared PHP library for building WordPress plugins that accept Bitcoin and Lightning payments. It handles the payment pipeline — API calls, webhook verification, settings pages, transaction tables, logging — so you can focus on what makes your plugin unique.

Use coinsnap-core when you are building a custom non-WooCommerce plugin (a donation form, paywall, crowdfunding widget, custom checkout, etc.). For WooCommerce, use the Coinsnap for WooCommerce plugin instead.


Requirements

  • PHP 7.4+
  • WordPress 6.0+
  • A Coinsnap Store ID and API key

Installation

Bundle coinsnap-core inside your plugin's vendor/ directory. It is not installed separately.

# Git submodule (recommended)
cd your-plugin/
git submodule add https://github.com/coinsnap/coinsnap-core.git vendor/coinsnap-core

Or download and extract manually to your-plugin/vendor/coinsnap-core/.

Then load it from your main plugin file:

require_once plugin_dir_path( __FILE__ ) . 'vendor/coinsnap-core/coinsnap-core.php';

The library includes a double-load guard (COINSNAP_CORE_VERSION), so it's safe to bundle in multiple plugins running on the same site simultaneously.


Setup — PluginInstance

Every consuming plugin starts by creating a PluginInstance. This is an immutable config object that all core classes read from — it's how multiple Coinsnap-based plugins coexist on the same site without collisions.

use CoinsnapCore\PluginInstance;

$instance = new PluginInstance( array(
'plugin_name' => 'My Bitcoin Plugin',
'option_key' => 'my_plugin_settings', // wp_options key for plugin settings
'webhook_key' => 'my_plugin_webhook', // wp_options key for webhook data
'table_suffix' => 'my_plugin_payments', // appended to $wpdb->prefix
'rest_namespace' => 'myplugin/v1', // WordPress REST API namespace
'referral_code' => 'YOUR_REFERRAL_CODE',
'menu_slug' => 'my-plugin',
) );

Pass this instance into every core class call — never use globals or singletons.


Creating an Invoice

Use ProviderFactory::create() to get the active provider, then call create_invoice():

use CoinsnapCore\Util\ProviderFactory;

$provider = ProviderFactory::create( $instance );

$invoice = $provider->create_invoice(
$form_id = 42, // your internal source ID (form, post, campaign, etc.)
$amount = 1500, // amount in cents — 1500 = 15.00
$currency = 'EUR',
$data = array(
'email' => 'buyer@example.com',
'metadata' => array( 'order_id' => 99 ), // anything you need back in the webhook
)
);

if ( empty( $invoice ) ) {
// Invoice creation failed — API credentials may be missing or incorrect
}

// Redirect the customer to the Coinsnap payment page
wp_redirect( $invoice['payment_url'] );

// Save $invoice['invoice_id'] to match with the webhook later

Return value:

KeyTypeDescription
invoice_idstringCoinsnap invoice ID — save this to correlate with incoming webhooks
payment_urlstringURL of the Coinsnap-hosted payment page — redirect the customer here

Returns an empty array on failure (missing credentials or API error).

Supported currencies: EUR, USD, SATS, BTC, CAD, JPY, GBP, CHF, RUB


Handling Webhooks

Register a REST route in WordPress, then use WebhookHelper to verify the signature and parse the event:

use CoinsnapCore\Rest\WebhookHelper;

// Register the route
add_action( 'rest_api_init', function () use ( $instance ) {
register_rest_route( $instance->rest_namespace(), '/webhook/coinsnap', array(
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => function ( WP_REST_Request $request ) use ( $instance ) {

// 1. Verify signature
if ( ! WebhookHelper::verify_coinsnap_signature( $instance ) ) {
return new WP_Error( 'invalid_signature', 'Unauthorized', array( 'status' => 401 ) );
}

// 2. Parse the event
$data = json_decode( $request->get_body(), true ) ?: array();
$parsed = WebhookHelper::parse_webhook( 'coinsnap', $data );

// 3. Act on settlement
if ( $parsed['paid'] ) {
// Fulfill: grant access, email customer, update your DB, etc.
do_action( 'my_plugin_payment_settled', $parsed['invoice_id'], $parsed['metadata'] );
}

return rest_ensure_response( array( 'received' => true ) );
},
) );
} );

parse_webhook() return value:

KeyTypeDescription
invoice_idstringThe Coinsnap invoice ID
paidbooltrue when the event type is Settled (or equivalent)
typestringRaw event type from the webhook payload
metadataarrayFull raw webhook payload

The webhook URL registered with Coinsnap will be:

https://yoursite.com/wp-json/{rest_namespace}/webhook/coinsnap

Webhook registration happens automatically when an admin clicks the connection check on the settings page.


Admin Pages

Drop in the built-in settings page, transactions list, and log viewer with a few lines:

use CoinsnapCore\Admin\SettingsPage;
use CoinsnapCore\Admin\TransactionsPage;
use CoinsnapCore\Admin\AjaxHandlers;

add_action( 'admin_menu', function () use ( $instance ) {
add_menu_page(
'My Plugin',
'My Plugin',
'manage_options',
'my-plugin',
function () use ( $instance ) { TransactionsPage::render_page_for( $instance ); }
);
add_submenu_page(
'my-plugin', 'Settings', 'Settings', 'manage_options', 'my-plugin-settings',
function () use ( $instance ) { SettingsPage::render_page_for( $instance ); }
);
} );

add_action( 'admin_init', function () use ( $instance ) {
SettingsPage::register_for( $instance );
} );

// AJAX handler for the connection check button on the settings page
add_action( 'wp_ajax_my_plugin_connection_check', function () use ( $instance ) {
AjaxHandlers::handle_connection_check( $instance );
} );

The settings page includes fields for Store ID, API key, log level, and webhook verification toggle. It also renders a connection check button that verifies credentials and registers the webhook automatically.


Database Table

Call PaymentTable::activate() on plugin activation to create the payments table:

use CoinsnapCore\Database\PaymentTable;

register_activation_hook( __FILE__, function () use ( $instance ) {
PaymentTable::activate( $instance );
} );

This creates {$wpdb->prefix}{table_suffix} with columns for source_id, invoice_id, amount, currency, payment_status, customer_email, and timestamps. The Transactions admin page reads from this table automatically.


Hooks

HookTypeArgsFires when
wpbn_coinsnap_responseaction$response, $form_idAfter any Coinsnap API call
wpbn_coinsnap_webhook_receivedaction$request_bodyWhen a webhook arrives, before parsing
coinsnap_core_transaction_sourcesfilterarray $sourcesPopulate the source dropdown in the Transactions page
coinsnap_core_transaction_source_labelfilterstring $label, int $source_idResolve a source ID to a display label

Example — wiring up a custom post type as the transaction source:

add_filter( 'coinsnap_core_transaction_sources', function ( $sources ) {
foreach ( get_posts( array( 'post_type' => 'my_campaign', 'posts_per_page' => -1 ) ) as $post ) {
$sources[] = array( 'id' => $post->ID, 'title' => $post->post_title );
}
return $sources;
} );

add_filter( 'coinsnap_core_transaction_source_label', function ( $label, $source_id ) {
$post = get_post( $source_id );
return $post ? $post->post_title : $label;
}, 10, 2 );

Minimal complete example

<?php
/**
* Plugin Name: My Bitcoin Plugin
* Requires PHP: 7.4
*/

require_once plugin_dir_path( __FILE__ ) . 'vendor/coinsnap-core/coinsnap-core.php';

use CoinsnapCore\PluginInstance;
use CoinsnapCore\Util\ProviderFactory;
use CoinsnapCore\Admin\SettingsPage;
use CoinsnapCore\Admin\AjaxHandlers;
use CoinsnapCore\Database\PaymentTable;
use CoinsnapCore\Rest\WebhookHelper;

add_action( 'plugins_loaded', function () {

$instance = new PluginInstance( array(
'plugin_name' => 'My Bitcoin Plugin',
'option_key' => 'mbp_settings',
'webhook_key' => 'mbp_webhook',
'table_suffix' => 'mbp_payments',
'rest_namespace' => 'mbp/v1',
'menu_slug' => 'mbp',
) );

register_activation_hook( __FILE__, fn() => PaymentTable::activate( $instance ) );

add_action( 'admin_init', fn() => SettingsPage::register_for( $instance ) );
add_action( 'wp_ajax_mbp_connection_check', fn() => AjaxHandlers::handle_connection_check( $instance ) );

add_action( 'rest_api_init', function () use ( $instance ) {
register_rest_route( $instance->rest_namespace(), '/webhook/coinsnap', array(
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => function ( WP_REST_Request $request ) use ( $instance ) {
if ( ! WebhookHelper::verify_coinsnap_signature( $instance ) ) {
return new WP_Error( 'unauthorized', 'Unauthorized', array( 'status' => 401 ) );
}
$parsed = WebhookHelper::parse_webhook( 'coinsnap', json_decode( $request->get_body(), true ) ?: array() );
if ( $parsed['paid'] ) {
// your fulfillment logic here
}
return rest_ensure_response( array( 'received' => true ) );
},
) );
} );

// Example shortcode that creates an invoice on form submit
add_shortcode( 'my_bitcoin_form', function () use ( $instance ) {
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
$provider = ProviderFactory::create( $instance );
$invoice = $provider->create_invoice( 0, 1000, 'EUR', array( 'email' => sanitize_email( $_POST['email'] ?? '' ) ) );
if ( ! empty( $invoice['payment_url'] ) ) {
wp_redirect( $invoice['payment_url'] );
exit;
}
}
return '<form method="post"><input name="email" type="email" required /><button type="submit">Pay €10</button></form>';
} );
} );