Official Go SDK for QBitFlow — a cryptocurrency payment processing platform supporting one-time payments, recurring subscriptions, refunds, and accounting exports.
- Installation
- Quick Start
- Configuration
- One-Time Payments
- Subscriptions
- Transaction Status
- Refunds
- Accounting Export
- Account Claims
- Customer Management
- Product Management
- User Management
- API Key Management
- Webhook Handling
- Error Handling
- Testing
go get github.com/QBitFlow/qbitflow-go-sdkpackage main
import (
"fmt"
"log"
"github.com/QBitFlow/qbitflow-go-sdk"
qbmodels "github.com/QBitFlow/qbitflow-go-sdk/pkg/models"
qbf "github.com/QBitFlow/qbitflow-go-sdk/pkg/qbitflow"
)
func main() {
client := qbitflow.New("your-api-key")
// Create a one-time payment session
session, err := client.Payments.CreateSession(&qbf.CreateSessionOptions{
ProductName: new("Premium Access"),
Description: new("One-time purchase"),
Price: new(49.99),
SuccessURL: new("https://yourapp.com/success"),
CancelURL: new("https://yourapp.com/cancel"),
CustomerUUID: new("customer-uuid"),
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Payment link: %s\n", session.Link)
// Check transaction status
status, err := client.TransactionStatus.GetTransactionStatus(
session.UUID,
qbmodels.TransactionTypeOneTimePayment,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %s\n", status.Status)
}// Minimal — API key only
client := qbitflow.New("your-api-key")
// Custom configuration
import "time"
client := qbitflow.NewWithConfig(qbf.Config{
APIKey: "your-api-key",
BaseURL: "https://api.qbitflow.app/v1", // optional, this is the default
Timeout: 30 * time.Second, // optional, default 30s
})
// Override base URL after creation (useful for testing)
client.SetBaseURL("http://localhost:3001")Provide either an existing ProductID or the (ProductName + Description + Price) triplet.
// Using an existing product
session, err := client.Payments.CreateSession(&qbf.CreateSessionOptions{
ProductID: new(uint64(42)),
SuccessURL: new("https://yourapp.com/success"),
CancelURL: new("https://yourapp.com/cancel"),
CustomerUUID: new("customer-uuid"),
})
// Using an inline product definition
session, err := client.Payments.CreateSession(&qbf.CreateSessionOptions{
ProductName: new("Custom Item"),
Description: new("Limited edition widget"),
Price: new(19.99),
SuccessURL: new("https://yourapp.com/success"),
CancelURL: new("https://yourapp.com/cancel"),
})session, err := client.Payments.GetSession("session-uuid")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Product: %s Price: $%.2f\n", session.ProductName, session.Price)
fmt.Printf("Is payment: %v\n", session.IsPayment())payment, err := client.Payments.GetPayment("payment-uuid")
fmt.Printf("Amount: $%.2f Hash: %s\n", payment.Amount, payment.TransactionHash)limit := uint16(20)
// One-time payments only
result, err := client.Payments.GetAllPayments(&limit, nil)
// Combined (one-time + subscription billing cycles)
combined, err := client.Payments.GetAllCombinedPayments(&limit, nil)
// Paginate
if result.HasMore() {
nextPage, err := client.Payments.GetAllPayments(&limit, result.NextCursor)
_ = nextPage
}customer, err := client.Payments.GetCustomerForTransaction("transaction-uuid")
fmt.Printf("%s %s — %s\n", customer.Name, customer.LastName, customer.Email)minPeriods := uint32(3)
session, err := client.Subscriptions.CreateSession(&qbf.CreateSubscriptionSessionOptions{
ProductID: new(uint64(1)),
Frequency: qbmodels.Duration{Value: 1, Unit: qbmodels.DurationUnitMonths},
TrialPeriod: &qbmodels.Duration{Value: 7, Unit: qbmodels.DurationUnitDays}, // optional
MinPeriods: &minPeriods, // optional: minimum periods before cancellation
SuccessURL: new("https://yourapp.com/success"),
CancelURL: new("https://yourapp.com/cancel"),
CustomerUUID: new("customer-uuid"),
})
fmt.Printf("Subscription link: %s\n", session.Link)Available frequency units: DurationUnitSeconds, DurationUnitMinutes, DurationUnitHours, DurationUnitDays, DurationUnitWeeks, DurationUnitMonths.
Webhook URLs are configured in the dashboard, not per session. Session creation no longer accepts a
webhookUrl. Set the Transaction webhook URL under settings in the QBitFlow dashboard — it applies consistently to every transaction.
sub, err := client.Subscriptions.GetSubscription("subscription-uuid")
fmt.Printf("Status: %s Next billing: %s\n",
sub.SubscriptionStatus, sub.NextBillingDate.Format("2006-01-02"))A subscription's status changes over its lifetime (trial, active, past_due, low_on_funds,
pending, cancelled, trial_expired).
Previously you had to run a cron job that periodically fetched each subscription and diffed its
status to react to these transitions. This is no longer necessary. Configure the
Subscription status webhook URL under settings in the QBitFlow dashboard
and QBitFlow will POST a SubscriptionStatusTransitionWebhook to your endpoint whenever a
subscription changes status:
type SubscriptionStatusTransitionWebhook struct {
SubscriptionUUID string // the subscription that changed
PreviousStatus SubscriptionStatus // status before the transition
CurrentStatus SubscriptionStatus // status after the transition
UpdatedAt time.Time // when the transition occurred
}See Webhook Handling for a full handler example.
Subscription statuses: SubscriptionStatusTrial, SubscriptionStatusActive, SubscriptionStatusPastDue, SubscriptionStatusLowOnFunds, SubscriptionStatusPending, SubscriptionStatusCancelled, SubscriptionStatusTrialExpired.
history, err := client.Subscriptions.GetPaymentHistory("subscription-uuid")
for _, record := range history {
fmt.Printf("%s — $%.2f on %s\n", record.UUID, record.Amount,
record.CreatedAt.Format("2006-01-02"))
}resp, err := client.Subscriptions.ExecuteTestBillingCycle("subscription-uuid")
fmt.Println(resp.Message)resp, err := client.Subscriptions.ForceCancel("subscription-uuid")
fmt.Println(resp.Message)status, err := client.TransactionStatus.GetTransactionStatus(
"transaction-uuid",
qbmodels.TransactionTypeOneTimePayment,
)
if err != nil {
log.Fatal(err)
}
switch status.Status {
case qbmodels.TransactionStatusCompleted:
fmt.Printf("Done! tx hash: %s\n", status.TxHash)
case qbmodels.TransactionStatusFailed:
fmt.Printf("Failed: %s\n", *status.Message)
}The SDK returns the WebSocket URL; connect to it with your preferred WebSocket library.
wsURL := client.TransactionStatus.GetTransactionStatusWSURL(
"transaction-uuid",
qbmodels.TransactionTypeOneTimePayment,
)
fmt.Println("Connect to:", wsURL)
// e.g. github.com/gorilla/websocket| Constant | Value |
|---|---|
TransactionTypeOneTimePayment |
"payment" |
TransactionTypeCreateSubscription |
"createSubscription" |
TransactionTypeCancelSubscription |
"cancelSubscription" |
TransactionTypeExecuteSubscriptionPayment |
"executeSubscription" |
TransactionTypeIncreaseAllowance |
"increaseAllowance" |
// Get the refund for a specific transaction (public)
refund, err := client.Refunds.GetByTransactionUUID("transaction-uuid")
fmt.Printf("Status: %s Reason: %s\n", refund.Status, refund.Reason)
// List all active refunds
refunds, err := client.Refunds.GetAll()
fmt.Printf("%d active refunds\n", len(refunds))
// List inactive (processed/rejected) refunds with pagination
limit := uint16(20)
result, err := client.Refunds.GetAllInactive(&limit, nil)
for _, r := range result.Items {
fmt.Printf("%s — %s\n", r.UUID, r.Status)
}Refund statuses: RefundStatusPending, RefundStatusApproved, RefundStatusRejected, RefundStatusFailed.
Export accounting events for a date range as JSON or CSV. Dates must be in YYYY-MM-DD format.
// JSON export — returns []AccountingEvent
events, err := client.Accounting.Export(qbf.AccountingExportParams{
From: "2025-01-01",
To: "2025-12-31",
Format: "json",
})
if err != nil {
log.Fatal(err)
}
for _, e := range events {
fmt.Printf("[%s] %s — gross $%.2f net $%.2f\n",
e.TxTimeUTC.Format("2006-01-02"), e.PaymentID,
e.GrossAmountUSD, e.NetAmountUSD)
}
// CSV export — returns raw CSV string
csv, err := client.Accounting.Export(qbf.AccountingExportParams{
From: "2025-01-01",
To: "2025-12-31",
Format: "csv",
})
if err != nil {
log.Fatal(err)
}
os.WriteFile("accounting_2025.csv", []byte(csv), 0644)Each AccountingEvent contains full transaction details: product, customer, blockchain info, fees (platform, organization, network), and gross/net amounts.
Organizations can create users before those users have claimed their accounts. During the unclaimed period all payments go to the organization and a ledger is kept. When the organization creates a claim request, the user receives a link to set up their password and wallet, then the owed funds are transferred.
// Create a claim request for a user (admin only) — returns the claim link
resp, err := client.Claims.CreateClaimRequest(userID)
fmt.Printf("Share this link with the user: %s\n", resp.Link)
// Get the existing claim request for a user (admin only) — same response as CreateClaimRequest
resp, err = client.Claims.GetClaimRequestByUserID(userID)
fmt.Printf("Existing claim link: %s\n", resp.Link)
// List funds the org owes to recently claimed users
funds, err := client.Claims.GetClaimFunds()
for _, f := range funds {
fmt.Printf("User %d owes $%.2f\n", f.UserID, f.TotalAmountOwed)
}
// Trigger test claim fund computation for a user (test mode only)
resp2, err := client.Claims.TriggerTestClaimFunds(userID)
fmt.Println(resp2.Message)// Create
customer, err := client.Customers.Create(&qbf.CreateCustomer{
Name: "Jane",
LastName: "Doe",
Email: "jane@example.com",
Reference: new("CRM-42"),
})
// Get by UUID or email
customer, err = client.Customers.Get("customer-uuid")
customer, err = client.Customers.GetByEmail("jane@example.com")
// List (paginated)
limit := uint16(50)
page, err := client.Customers.GetAll(&limit, nil)
if page.HasMore() {
next, err := client.Customers.GetAll(&limit, page.NextCursor)
_ = next
}
// Update — UUID is the first argument, not a field on the struct
updated, err := client.Customers.Update("customer-uuid", &qbf.UpdateCustomer{
Name: "Jane",
LastName: "Smith",
Email: "jane.smith@example.com",
})
// Delete
err = client.Customers.Delete("customer-uuid")ref := "PROD-001"
// Create
product, err := client.Products.Create(&qbf.CreateProduct{
Name: "Premium Plan",
Description: "Access to all features",
Price: 29.99,
Reference: &ref, // optional, auto-generated if omitted
})
// Get
product, err = client.Products.Get(product.ID)
product, err = client.Products.GetByReference("PROD-001")
// List all
products, err := client.Products.GetAll()
// Update
updated, err := client.Products.Update(product.ID, &qbf.UpdateProduct{
Name: "Premium Plus",
Description: "Enhanced features",
Price: 39.99,
})
// Delete
err = client.Products.Delete(product.ID)// Create (admin only)
user, err := client.Users.Create(&qbf.CreateUser{
Name: "Alice",
LastName: "Smith",
Email: "alice@example.com",
Role: "user", // "user" or "admin"
OrganizationFeeBps: 100, // 1% fee, optional
})
// Get current user (from API key)
me, err := client.Users.Get()
// Get by ID (admin only)
user, err = client.Users.GetByID(42)
// List all (admin only)
users, err := client.Users.GetAll()
// Update
updated, err := client.Users.Update(user.ID, &qbf.UpdateUser{
Name: "Alicia",
LastName: "Smith",
Email: user.Email,
})
// Delete (admin only)
err = client.Users.Delete(user.ID)// Create
resp, err := client.ApiKeys.Create(&qbf.CreateApiKeyDto{
Name: "Production Key",
UserID: userID,
Test: false,
})
fmt.Printf("Key (only shown once): %s\n", resp.Key)
// List for current user
keys, err := client.ApiKeys.GetAll()
// List for a specific user (admin only)
keys, err = client.ApiKeys.GetForUser(userID)
// Delete
err = client.ApiKeys.Delete(keyID)Webhook destination URLs are configured under settings in the QBitFlow dashboard, not in code. There are two independent webhooks you can enable:
| Dashboard setting | Payload type | Fired when |
|---|---|---|
| Transaction webhook | SessionWebhookResponse |
a payment or subscription transaction changes status |
| Subscription status webhook | SubscriptionStatusTransitionWebhook |
a subscription transitions between statuses (e.g. trial → active, active → past_due) |
Every webhook is signed with HMAC-SHA256. Verify it with client.Webhooks.Verify before trusting
the payload. Responding with a status >= 400 tells QBitFlow to retry delivery.
Webhook headers: X-Webhook-Signature-256, X-Webhook-Timestamp, X-Webhook-ID.
The dashboard's "Test webhook" action sends a fake payload to your configured URL to confirm the
endpoint is reachable. This payload may not match a real webhook's shape, so your handler should
short-circuit it: when the incoming X-Webhook-ID header equals qbf.TEST_WEBHOOK_ID, return
HTTP 200 immediately and skip normal processing.
package main
import (
"encoding/json"
"io"
"net/http"
"github.com/QBitFlow/qbitflow-go-sdk"
qbmodels "github.com/QBitFlow/qbitflow-go-sdk/pkg/models"
qbf "github.com/QBitFlow/qbitflow-go-sdk/pkg/qbitflow"
)
var sdkClient = qbitflow.New("your-api-key")
func transactionWebhookHandler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
signature := r.Header.Get(sdkClient.Webhooks.GetSignatureHeader())
timestamp := r.Header.Get(sdkClient.Webhooks.GetTimestampHeader())
webhookID := r.Header.Get(sdkClient.Webhooks.GetWebhookIDHeader())
// Verify authenticity — a >= 400 response causes QBitFlow to retry.
valid, err := sdkClient.Webhooks.Verify(body, signature, timestamp)
if err != nil || !valid {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// Reachability check from the dashboard "Test webhook" action.
if webhookID == qbf.TEST_WEBHOOK_ID {
w.WriteHeader(http.StatusOK)
return
}
var event qbmodels.SessionWebhookResponse
if err := json.Unmarshal(body, &event); err != nil {
http.Error(w, "bad payload", http.StatusBadRequest)
return
}
switch event.Status.Status {
case qbmodels.TransactionStatusCompleted:
// Fulfill order, update DB, etc.
case qbmodels.TransactionStatusFailed:
// Notify customer
}
w.WriteHeader(http.StatusOK)
}Configure the Subscription status webhook in the dashboard to receive lifecycle transitions. This replaces polling each subscription on a cron schedule to detect status changes.
func subscriptionStatusWebhookHandler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
signature := r.Header.Get(sdkClient.Webhooks.GetSignatureHeader())
timestamp := r.Header.Get(sdkClient.Webhooks.GetTimestampHeader())
webhookID := r.Header.Get(sdkClient.Webhooks.GetWebhookIDHeader())
valid, err := sdkClient.Webhooks.Verify(body, signature, timestamp)
if err != nil || !valid {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// Reachability check from the dashboard "Test webhook" action.
if webhookID == qbf.TEST_WEBHOOK_ID {
w.WriteHeader(http.StatusOK)
return
}
var event qbmodels.SubscriptionStatusTransitionWebhook
if err := json.Unmarshal(body, &event); err != nil {
http.Error(w, "bad payload", http.StatusBadRequest)
return
}
switch event.CurrentStatus {
case qbmodels.SubscriptionStatusActive:
// Trial converted or payment recovered — grant/keep access.
case qbmodels.SubscriptionStatusPastDue, qbmodels.SubscriptionStatusLowOnFunds:
// Prompt the customer to top up their allowance.
case qbmodels.SubscriptionStatusCancelled, qbmodels.SubscriptionStatusTrialExpired:
// Revoke access.
}
w.WriteHeader(http.StatusOK)
}import qberrors "github.com/QBitFlow/qbitflow-go-sdk/pkg/errors"
payment, err := client.Payments.GetPayment("uuid")
if err != nil {
switch e := err.(type) {
case *qberrors.NotFoundError:
fmt.Println("not found")
case *qberrors.ValidationError:
fmt.Printf("validation: %s\n", e.Message)
case *qberrors.QBitFlowError:
fmt.Printf("API error %d: %s\n", e.StatusCode, e.Message)
default:
fmt.Printf("unexpected: %v\n", err)
}
}This project is licensed under the MPL-2.0 License - see the LICENSE file for details.
See CHANGELOG.md for version history.
For security issues, please email security@qbitflow.app instead of using the issue tracker.