Developers
An API you can read end to end.
Contract-first OpenAPI, idempotent writes, signed webhooks and custodial processing through Bitcart. Everything below uses placeholder keys — live keys are created in the dashboard after approval and shown once.
Quickstart
Create your first payment intent.
Each example creates the same payment intent and redirects to the hosted checkout. They work once live payments are enabled for your organisation.
curl -X POST https://api.feitpay.com/v1/payment-intents \
-H "Authorization: Bearer gp_live_YOUR_KEY" \
-H "Idempotency-Key: order-123" \
-H "Content-Type: application/json" \
-d '{
"store_id": "store_YOUR_STORE_ID",
"amount": "59.90",
"currency": "EUR",
"allowed_methods": ["crypto"],
"merchant_order_id": "order-123",
"return_url": "https://merchant.example/thank-you"
}'import { Gryphem } from '@gryphem-pay/sdk';
const gp = new Gryphem({ apiKey: process.env.GRYPHEM_API_KEY!, baseUrl: 'https://api.feitpay.com' }); // gp_live_…
const intent = await gp.paymentIntents.create(
{
store_id: 'store_YOUR_STORE_ID',
amount: '59.90',
currency: 'EUR',
merchant_order_id: 'order-123',
},
{ idempotencyKey: 'order-123' },
);
redirect(intent.checkout_url);client, err := gryphempay.New(os.Getenv("GRYPHEM_API_KEY"), gryphempay.WithBaseURL("https://api.feitpay.com"))
if err != nil {
return err
}
intent, err := client.PaymentIntents.Create(ctx, &gryphempay.PaymentIntentCreateParams{
StoreID: "store_YOUR_STORE_ID",
Amount: "59.90",
Currency: "EUR",
MerchantOrderID: "order-123",
IdempotencyKey: "order-123",
})
if err != nil {
return err
}
http.Redirect(w, r, *intent.CheckoutURL, http.StatusSeeOther)from gryphem_pay import Gryphem
gp = Gryphem(api_key=os.environ["GRYPHEM_API_KEY"], base_url="https://api.feitpay.com") # gp_live_…
intent = gp.payment_intents.create(
store_id="store_YOUR_STORE_ID",
amount="59.90",
currency="EUR",
merchant_order_id="order-123",
idempotency_key="order-123",
)
return redirect(intent["checkout_url"])SDK availability and versions are listed in the SDK documentation. The Go module path is a placeholder until the SDK is published.
Webhooks
Verify, deduplicate, then act.
Signatures are HMAC-SHA256 over the timestamp and the raw body. Event IDs are stable across retries.
import { verifyWebhook, WebhookVerificationError } from '@gryphem-pay/webhooks';
app.post('/webhooks/gryphem', express.raw({ type: 'application/json' }), async (req, res) => {
try {
const event = await verifyWebhook({
payload: req.body, // raw Buffer — never re-serialised JSON
header: req.get('Gryphem-Signature') ?? '',
secrets: [process.env.GRYPHEM_WEBHOOK_SECRET!], // add the previous secret during rotation
});
if (await seen(event.event_id)) return res.sendStatus(200); // idempotent
if (event.event_type === 'payment.confirmed') await markOrderPaid(event.object.id);
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookVerificationError) return res.sendStatus(400);
throw err;
}
});Conventions
Predictable by design.
- Idempotency
- Send an Idempotency-Key on every create. Replays return the original response; a reused key with a different body is rejected.
- Typed errors
- Errors carry type, code, message, param and request_id. Retry only rate_limit_error and api_error, with backoff.
- Cursor pagination
- Lists accept limit and starting_after and return has_more and next_cursor — stable under concurrent writes.
- Money as strings
- Amounts are decimal strings; crypto amounts also come in integer base units. Never parse them into floats.
- Request IDs
- Every response has a Request-Id header. Quote it in support requests; it links to the request log in the dashboard.
- Versioning
- The API is versioned under /v1. Webhook payloads carry api_version so receivers can migrate deliberately.
Going live
Ready before the first payment.
Create your account and submit business verification now. Before your first live payment, make sure your integration handles every state: underpaid, overpaid, expired and manual review reach a person, and you fulfil only when a payment is fulfillable.