Skip to content
chainkit / signal · providers — · 24h fleet telemetry
p95 ·
Docs · payments quickstart

Take Bitcoin payments in an afternoon.

The whole integration is: register an xpub, mint an API key, POST one JSON body, verify one webhook. No custody paperwork, because there's no custody — every invoice settles straight to an address derived from your wallet. Start on testnet; everything below works identically on mainnet.

01–04 · in the console (~10 minutes)

  1. 01

    Create an account + project

    Sign up free — no card, no business KYC (chainkit never holds funds, so there is nothing to vet). Your personal workspace and first project are created automatically; everything below happens inside that project.

  2. 02

    Set up your business profile

    The name, address, country, and VAT id that appear on invoices and receipts. Snapshotted onto each invoice at issue time, so editing the profile later never rewrites history.

  3. 03

    Register a wallet (xpub — never a seed)

    Paste an extended PUBLIC key from your wallet: xpub / ypub / zpub on mainnet, or tpub / upub / vpub on testnet — strongly recommended for your first run. chainkit derives a fresh customer-payment address from it per invoice; your private keys never leave your wallet.

  4. 04

    Verify ownership

    Open your wallet's Receive tab and paste any address it shows. chainkit confirms it derives from the xpub you registered — proof that customer payments will land in a wallet you control. This is the gate before any real money moves.

The console walks you through all four with live status checks — this page exists so you can judge the work before signing up.

05 · create your first invoice

Mint a project-scoped API key in the console, then it's one authenticated POST. Price in fiat and chainkit locks the BTC amount at issuance — or send amount_sats directly if you'd rather own the rate.

create-invoice.sh
curl -X POST 'https://api.chainkit.dev/v1/projects/YOUR_PROJECT_ID/payments/invoices' \
  -H 'Authorization: Bearer ck_test_PASTE_YOUR_KEY_HERE' \
  -H 'Content-Type: application/json' \
  -d '{
    "xpub_id": "<your_verified_xpub_id>",
    "amount_fiat_cents": 4900,
    "fiat_currency": "EUR",
    "memo": "order-1042"
  }'
main.go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

func main() {
    // Fiat-priced: chainkit locks a BTC rate at issuance and freezes
    // the sats amount. (Advanced override: send "amount_sats" instead.)
    body, _ := json.Marshal(map[string]any{
        "xpub_id":           "<your_verified_xpub_id>",
        "amount_fiat_cents": 4900,
        "fiat_currency":     "EUR",
        "memo":              "order-1042",
    })

    req, _ := http.NewRequest("POST",
        "https://api.chainkit.dev/v1/projects/YOUR_PROJECT_ID/payments/invoices",
        bytes.NewReader(body))
    req.Header.Set("Authorization", "Bearer ck_test_PASTE_YOUR_KEY_HERE")
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    out, _ := io.ReadAll(resp.Body)
    fmt.Println(resp.Status)
    fmt.Println(string(out))
    // The response carries "public_id" — your customer pays at
    // https://pay.chainkit.dev/p/<public_id> (QR, live status, receipt).
    // "address" is a fresh address derived from YOUR xpub. No reuse.
}

The response includes public_id — send your customer to pay.chainkit.dev/p/<public_id> for a hosted checkout with QR, live payment status, and a PDF receipt. The address field is derived fresh from your xpub for every invoice: no address reuse, no clustering.

06 · trust the webhook, fulfil the order

Register a webhook endpoint in the console and chainkit signs every delivery (X-Chainkit-Signature, HMAC with a timestamp). The Go SDK ships the verifier — go get github.com/exapsy/chainkit. Fulfil on invoice.confirmed, never on invoice.paid alone: paid means seen, confirmed means the money is irreversibly in your wallet.

webhook.go
package main

import (
    "io"
    "log"
    "net/http"
    "os"

    "github.com/exapsy/chainkit/payment"
)

func main() {
    // The signing secret shown when you create the webhook endpoint.
    secret := os.Getenv("CHAINKIT_WEBHOOK_SECRET")

    http.HandleFunc("/webhooks/chainkit", func(w http.ResponseWriter, r *http.Request) {
        body, err := io.ReadAll(r.Body)
        if err != nil {
            w.WriteHeader(http.StatusBadRequest)
            return
        }

        // HMAC + timestamp verification (constant-time, 5 min replay
        // window) — never trust an unverified webhook.
        sig := r.Header.Get("X-Chainkit-Signature")
        if err := payment.VerifyWebhook(body, sig, secret); err != nil {
            w.WriteHeader(http.StatusUnauthorized)
            return
        }

        switch r.Header.Get("X-Chainkit-Event") {
        case "invoice.paid":
            // Payment seen (possibly 0-conf) — NOT yet safe to fulfil.
        case "invoice.confirmed":
            // Confirmed on-chain. The sats are already in YOUR wallet —
            // safe to ship the goods.
            log.Println("order settled:", string(body))
        case "invoice.expired", "invoice.cancelled":
            // Dead invoice — release held stock, offer a new link.
        }
        w.WriteHeader(http.StatusOK)
    })

    log.Fatal(http.ListenAndServe(":8081", nil))
}

That's the whole integration.

Start free

Next steps: the payments API reference covers exports, refund record-keeping, and the payments endpoints in full; pricing is one plan billed on what you settle; and security explains the non-custodial model in depth. Your customer's side of the story is at paying with Bitcoin.