Documentatie / Aan de slag

Snelstart

Uw eerste betaling in de sandbox binnen 15 minuten. U hebt een server met Node.js, PHP, Python of Go nodig — er zijn voorbeelden voor alle vier.

Het eindresultaatUw website maakt een factuur aan, de klant betaalt op de pagina van Coinetic (in de sandbox met een knop), uw server ontvangt een webhook en levert de bestelling precies één keer.

1. Haal uw sandboxsleutels op

  1. Registreer u in het sandbox-dashboard en schakel 2FA in.
  2. ‘Sleutels en webhooks’ → ‘Sleutel aanmaken’: bewaar de key_id (ak_…) en het secret.
  3. ‘Sleutels en webhooks’ → ‘Endpoint toevoegen’: de URL van uw handler, bijvoorbeeld https://shop.example/webhooks/coinetic (in de sandbox mag ook http://localhost:3000/…). Bewaar het webhook-secret.

Secrets worden één keer getoond. Zet ze in de omgevingsvariabelen van uw server:

.env (niet committen!)
API_KEY=ak_...
API_SECRET=...
WEBHOOK_SECRET=...

2. Download de client

De client is één bestand zonder externe dependencies: requests ondertekenen, facturen aanmaken en opvragen, webhooks verifiëren. Download voor uw taal →

3. Maak een factuur aan

De prijs als string, in USD, EUR, USDT of USDC. Baseer de Idempotency-Key op het ordernummer: een retry na een time-out geeft dan dezelfde factuur terug.

create-invoice.js
import { CoineticClient } from "./cryptopay.js";

const cp = new CoineticClient({ baseUrl: "https://sandbox.coinetic.money", keyId: process.env.API_KEY, secret: process.env.API_SECRET });

const invoice = await cp.createInvoice({
  order_id: "ORDER-1001",                 // uw ordernummer, uniek per merchant
  price_amount: "25.00",                  // string, geen getal
  price_currency: "USD",
  return_url: "https://shop.example/orders/ORDER-1001",
  metadata: { user_id: "42" },
}, "order-ORDER-1001");                   // Idempotency-Key

console.log(invoice.checkout_url);        // https://sandbox.coinetic.money/pay/inv_…#t=…
create-invoice.php
require 'Cryptopay.php';

$cp = new \Cryptopay\Client('https://sandbox.coinetic.money', getenv('API_KEY'), getenv('API_SECRET'));

$invoice = $cp->createInvoice([
    'order_id'       => 'ORDER-1001',
    'price_amount'   => '25.00',
    'price_currency' => 'USD',
    'return_url'     => 'https://shop.example/orders/ORDER-1001',
    'metadata'       => ['user_id' => '42'],
], 'order-ORDER-1001');

echo $invoice['checkout_url'];
create_invoice.py
import os
from cryptopay import CoineticClient

cp = CoineticClient("https://sandbox.coinetic.money", os.environ["API_KEY"], os.environ["API_SECRET"])

invoice = cp.create_invoice({
    "order_id": "ORDER-1001",
    "price_amount": "25.00",
    "price_currency": "USD",
    "return_url": "https://shop.example/orders/ORDER-1001",
    "metadata": {"user_id": "42"},
}, "order-ORDER-1001")

print(invoice["checkout_url"])
main.go
cp := &cryptopayclient.Client{
	BaseURL: "https://sandbox.coinetic.money",
	KeyID:   os.Getenv("API_KEY"),
	Secret:  os.Getenv("API_SECRET"),
	HTTP:    &http.Client{Timeout: 10 * time.Second},
}
inv, err := cp.CreateInvoice(ctx, cryptopayclient.CreateInvoice{
	OrderID:       "ORDER-1001",
	PriceAmount:   "25.00",
	PriceCurrency: "USD",
	ReturnURL:     "https://shop.example/orders/ORDER-1001",
	Metadata:      map[string]string{"user_id": "42"},
}, "order-ORDER-1001")
if err != nil {
	return err
}
fmt.Println(inv.CheckoutURL)

4. Stuur de klant naar de betaalpagina

Redirect de browser naar invoice.checkout_url (HTTP 303). De klant kiest een valuta en ziet het adres, het bedrag en een QR-code. In de sandbox staan onder de betaalgegevens knoppen voor testbetalingen: ‘Exact betalen’, ‘Onderbetaling 10%’, ‘Overbetaling 10%’, ‘Half bedrag’, ‘Restant bijbetalen’.

Niet in een iframe insluitenDe betaalpagina staat insluiten niet toe — dat beschermt tegen het vervangen van het adres. Open haar in hetzelfde tabblad of in de systeembrowser van uw app.

5. Ontvang de webhook

De volgorde is altijd dezelfde: handtekening controleren → herhaling negeren → status opnieuw controleren via de API → bestelling één keer leveren → antwoorden met 2xx.

webhook.js
import express from "express";
import { verifyWebhook } from "./cryptopay.js";
// cp — de client uit stap 3. alreadyProcessed / markProcessed / fulfil — uw eigen functies.

const app = express();

// express.raw: de handtekening wordt over de ruwe bytes berekend — gebruik op deze route geen express.json()
app.post("/webhooks/coinetic", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = verifyWebhook(process.env.WEBHOOK_SECRET, req.headers, req.body);
  } catch (e) {
    return res.status(e.status ?? 400).end();               // 401 — onbekende handtekening, 400 — verouderde timestamp
  }
  try {
    const eventId = req.headers["x-webhook-id"];
    if (await alreadyProcessed(eventId)) return res.sendStatus(200);
    const inv = await cp.getInvoice(event.data.invoice_id); // status uit de API, niet uit de webhook
    if (inv.status === "paid" || inv.status === "overpaid") await fulfil(inv.order_id);
    await markProcessed(eventId);
    res.sendStatus(200);
  } catch (e) {
    console.error(e);
    res.sendStatus(500);                                    // geen 2xx — Coinetic probeert opnieuw
  }
});

app.listen(3000);
webhook.php
require 'Cryptopay.php';

$headers = array_change_key_case(getallheaders(), CASE_LOWER);
$raw = file_get_contents('php://input');                   // ruwe body, vóór json_decode
try {
    $event = \Cryptopay\verify_webhook(getenv('WEBHOOK_SECRET'), $headers, $raw);
} catch (\Cryptopay\CryptopayException $e) {
    http_response_code($e->status);
    exit;
}
if (already_processed($headers['x-webhook-id'])) { http_response_code(200); exit; }

$inv = $cp->getInvoice($event['data']['invoice_id']);       // status uit de API
if (in_array($inv['status'], \Cryptopay\PAID_STATUSES, true)) {
    fulfil($inv['order_id']);
}
mark_processed($headers['x-webhook-id']);
http_response_code(200);
webhook.py
import os
from flask import Flask, request
from cryptopay import verify_webhook, CryptopayError, PAID_STATUSES

app = Flask(__name__)

@app.post("/webhooks/coinetic")
def webhook():
    headers = {k.lower(): v for k, v in request.headers.items()}
    try:
        event = verify_webhook(os.environ["WEBHOOK_SECRET"], headers, request.get_data())
    except CryptopayError as e:
        return "", e.status
    if already_processed(headers["x-webhook-id"]):
        return "", 200
    inv = cp.get_invoice(event["data"]["invoice_id"])      # status uit de API
    if inv["status"] in PAID_STATUSES:
        fulfil(inv["order_id"])
    mark_processed(headers["x-webhook-id"])
    return "", 200
webhook.go
func webhook(w http.ResponseWriter, r *http.Request) {
	raw, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	if err := cryptopayclient.VerifyWebhook(os.Getenv("WEBHOOK_SECRET"), r.Header, raw, time.Now(), 5*time.Minute); err != nil {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}
	var ev struct {
		Data struct{ InvoiceID string `json:"invoice_id"` } `json:"data"`
	}
	if err := json.Unmarshal(raw, &ev); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	inv, err := cp.GetInvoice(r.Context(), ev.Data.InvoiceID) // status uit de API
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError) // Coinetic probeert opnieuw
		return
	}
	if cryptopayclient.PaidStatuses[inv.Status] {
		fulfilOnce(inv.OrderID)
	}
	w.WriteHeader(http.StatusOK)
}

6. Stuur de klant terug

Na de betaling verschijnt op de pagina de knop ‘Terug naar de winkel’, die naar uw return_url leidt. Toon daar de status van de bestelling uit uw eigen database. Is de webhook nog niet binnen, controleer dan GET /v1/invoices/{id}.

Volgende stappen