A production-grade, idiomatic Go SDK for the complete Revolut Developer API platform.
| API | Package | Methods | Key Resources |
|---|---|---|---|
| Merchant API (v2025-12-04) | merchant |
58 | Orders (CRUD + capture + refund + incremental auth), Payments, Customers + saved methods, Subscription Plans (variations+phases), Subscriptions + billing cycles, Payouts, Disputes (accept/evidence/challenge), Report Runs, Webhooks + rotate secret, Locations, Synchronous Webhooks (Fast Checkout) |
| Business API | business |
53 | Accounts + bank details, Cards (freeze/unfreeze/terminate), Counterparties (CoP validation), Expenses, FX (rate+exchange), Payment Drafts, Payout Links, Team Members, Transactions (+ by-request-id + cancel), Transfers + payments + card transfers, Webhooks v1+v2 (rotate secret + failed events) |
| Open Banking API | openbanking |
31 | AISP: accounts, balances, beneficiaries, direct debits, standing orders, transactions. PISP: domestic, domestic scheduled, domestic standing orders, international, international scheduled, international standing orders, file payments (bulk CSV, beta) |
| Crypto Ramp API | cryptoramp |
13 | Config, quote, buy redirect URL, orders (get+list), webhooks (full CRUD), signature verification + typed payload parsing |
| Crypto Exchange REST API | cryptoexchange |
12 | Balances, orders (market/limit/TPSL + cancel all), trades (public + private fills), order book, ticker (single + all), symbols |
| Webhook handler | webhook |
middleware | HMAC verification (Revolut's v1.{ts}.{body} format), typed event dispatch, replay-attack protection |
Total: 167 public methods · 174 struct types · 37 webhook event types · 6,953 lines · 38/38 tests pass
- Zero external dependencies — only the Go standard library
- Context-first — every method accepts
context.Context - Correct HMAC — uses Revolut's exact
v1.{timestamp}.{body}webhook signature format - Functional options — composable
WithXxxoptions on all clients - Generics for pagination —
types.PageResponse[T]across all paginated endpoints - Structured errors —
*APIError,*ValidationError,*SDKErrorwitherrors.Assupport - Retry + jitter — exponential backoff with full jitter, configurable per-call
- Token-bucket rate limiting — client-side, zero external deps
- Telemetry hooks — plug in your own logger/metrics via
telemetry.Hook - Replay-attack protection — optional 5-minute timestamp window on webhook handler
go get github.com/iamkanishka/revolut-gopackage main
import (
"context"
"log"
revolut "github.com/iamkanishka/revolut-go"
"github.com/iamkanishka/revolut-go/merchant"
"github.com/iamkanishka/revolut-go/types"
)
func main() {
sdk, err := revolut.New(
revolut.WithMerchantKey("sk_live_..."),
revolut.WithBusinessKey("biz_access_token"),
revolut.WithEnvironment(types.EnvProduction),
revolut.WithRateLimit(50, 100),
)
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
// Create an order
order, err := sdk.Merchant.CreateOrder(ctx, &merchant.CreateOrderRequest{
Amount: 1000, // £10.00
Currency: "GBP",
Description: "Widget purchase",
})
if err != nil {
log.Fatal(err)
}
log.Printf("order: %s checkout: %s", order.ID, order.CheckoutURL)
}sdk, err := revolut.New(
revolut.WithMerchantKey("sk_sandbox_..."),
revolut.WithSandbox(),
)import revolverrors "github.com/iamkanishka/revolut-go/errors"
order, err := client.GetOrder(ctx, "ord_missing")
if err != nil {
switch {
case revolverrors.IsValidationError(err):
log.Printf("bad input: %v", err) // caught before HTTP call
default:
if apiErr := revolverrors.AsAPIError(err); apiErr != nil {
log.Printf("API %d [%s] request_id=%s: %s",
apiErr.StatusCode, apiErr.Code, apiErr.RequestID, apiErr.Message)
if apiErr.IsNotFound() { /* handle missing resource */ }
if apiErr.IsRateLimited() { /* back off */ }
if apiErr.IsRetryable() { /* 5xx or 429 */ }
}
}
}import (
"github.com/iamkanishka/revolut-go/webhook"
"github.com/iamkanishka/revolut-go/types"
)
h := webhook.NewHandler(
// wsk_... signing secret from Revolut dashboard
webhook.WithSecret("wsk_VsuFcq6FIpa9gOWUu0n2WxiCbsDHIJlN"),
// Reject events older than 5 minutes (anti-replay)
webhook.WithTimestampValidation(),
webhook.WithErrorHandler(func(ctx context.Context, err error) {
slog.ErrorContext(ctx, "webhook error", "err", err)
}),
)
// Revolut uses: HMAC-SHA256("v1.{Revolut-Request-Timestamp}.{body}") → v1={hex}
// The handler reads both Revolut-Signature and Revolut-Request-Timestamp headers automatically.
webhook.On(h, types.EventOrderCompleted, func(ctx context.Context, evt *webhook.OrderCompletedEvent) error {
log.Printf("order %s completed: %d %s", evt.OrderID, evt.Amount, evt.Currency)
return fulfillOrder(ctx, evt.OrderID)
})
webhook.On(h, types.EventDisputeActionRequired, func(ctx context.Context, evt *webhook.DisputeEvent) error {
return notifyTeam(ctx, evt.DisputeID)
})
webhook.On(h, types.EventSubscriptionInitiated, func(ctx context.Context, evt *webhook.SubscriptionEvent) error {
return activateSubscription(ctx, evt.SubscriptionID)
})
http.Handle("/webhooks/revolut", h)// Create a plan with monthly and yearly variations, each with a trial phase + billing phase
plan, err := sdk.Merchant.CreatePlanV2(ctx, &merchant.CreatePlanV2Request{
Name: "Pro Plan",
TrialDuration: "P14D", // 14-day free trial
Variations: []merchant.PlanVariation{
{
Name: "Monthly",
Phases: []merchant.PlanPhase{
{Ordinal: 1, CycleDuration: "P1M", CycleCount: intPtr(1), Amount: 0, Currency: "GBP"}, // trial
{Ordinal: 2, CycleDuration: "P1M", Amount: 999, Currency: "GBP"}, // £9.99/mo
},
},
{
Name: "Yearly",
Phases: []merchant.PlanPhase{
{Ordinal: 1, CycleDuration: "P1Y", Amount: 9900, Currency: "GBP"}, // £99/yr
},
},
},
})
// Subscribe a customer (with hosted payment page redirect)
sub, err := sdk.Merchant.CreateSubscriptionV2(ctx, &merchant.CreateSubscriptionV2Request{
PlanVariationID: plan.Variations[0].ID, // monthly
CustomerID: "cust_abc",
SetupOrderRedirectURL: "https://example.com/subscription/success",
})
// sub.SetupOrderID → use GetOrder(sub.SetupOrderID) to get checkout_url
// List billing cycles
cycles, err := sdk.Merchant.ListBillingCycles(ctx, sub.ID, types.PageRequest{Limit: 10})// Register your HTTPS endpoint to receive shipping address validation requests
sw, err := sdk.Merchant.RegisterAddressValidation(ctx, &merchant.RegisterAddressValidationRequest{
EventType: "fast_checkout.validate_address",
URL: "https://your-backend.com/validate-address",
})
// sw.SigningKey → store this to verify incoming Revolut-Pay-Payload-Signature headers
log.Printf("signing key: %s", sw.SigningKey)
// List all registered synchronous webhooks
hooks, err := sdk.Merchant.ListSynchronousWebhooks(ctx)// Create v2 webhook (recommended version)
wh, err := sdk.Business.CreateWebhookV2(ctx, &business.CreateWebhookV2Request{
URL: "https://example.com/business-events",
Events: []types.WebhookEventType{
business.BizEventTransactionCreated,
business.BizEventTransactionStateChanged,
business.BizEventPayoutLinkCreated,
},
})
// Rotate signing secret (grace period: 1 day)
rotated, err := sdk.Business.RotateWebhookSigningSecretV2(ctx, wh.ID,
&business.RotateWebhookSigningSecretV2Request{
ExpirationPeriod: "P1D", // old secret valid for 1 day during transition
})
log.Printf("new secret: %s", rotated.SigningSecret)
// Retrieve failed delivery events
events, err := sdk.Business.GetFailedWebhookEvents(ctx, wh.ID,
business.ListFailedWebhookEventsRequest{Limit: 20})// Create a pre-auth order (e.g. for hotel holds)
order, err := sdk.Merchant.CreateOrder(ctx, &merchant.CreateOrderRequest{
Amount: 10000, // initial authorised amount
Currency: "GBP",
CaptureMode: types.CaptureModeManual,
// authorisation_type: pre_authorisation is set in the raw request body
})
// Increase the authorised amount (e.g. minibar charges added at checkout)
order, err = sdk.Merchant.IncrementalAuthorise(ctx, order.ID,
&merchant.IncrementalAuthorisationRequest{
Amount: 15000, // new TOTAL authorised amount (not delta)
Currency: "GBP",
Reference: "invoice_123",
})
// Fires: ORDER_INCREMENTAL_AUTHORISATION_AUTHORISED webhookreq := merchant.ListOrdersRequest{Page: types.PageRequest{Limit: 50}}
for {
page, err := client.ListOrders(ctx, req)
if err != nil {
return err
}
for _, order := range page.Items {
process(order)
}
if !page.HasNextPage() {
break
}
req.Page.Cursor = page.NextCursor
}import "github.com/iamkanishka/revolut-go/internal/retry"
sdk, _ := revolut.New(
revolut.WithMerchantKey("sk_live_..."),
revolut.WithRetryPolicy(retry.Policy{
MaxAttempts: 5,
InitialInterval: 200 * time.Millisecond,
MaxInterval: 30 * time.Second,
Multiplier: 2.0,
JitterFactor: 0.5,
}),
revolut.WithRateLimit(100, 200),
)import "github.com/iamkanishka/revolut-go/internal/telemetry"
sdk, _ := revolut.New(
revolut.WithMerchantKey("sk_live_..."),
revolut.WithTelemetry(telemetry.Hook{
OnRequest: func(ctx context.Context, e telemetry.RequestEvent) { /* log */ },
OnResponse: func(ctx context.Context, e telemetry.ResponseEvent) { /* metrics */ },
OnError: func(ctx context.Context, e telemetry.ErrorEvent) { /* alert */ },
}),
)revolut-go/
├── revolut.go # Unified SDK entry point
├── merchant/merchant.go # Merchant API — 58 methods
├── business/business.go # Business API — 53 methods
├── openbanking/openbanking.go # Open Banking API — 31 methods
├── cryptoramp/cryptoramp.go # Crypto Ramp API — 13 methods
├── cryptoexchange/cryptoexchange.go # Crypto Exchange REST API — 12 methods
├── webhook/webhook.go # Webhook handler + typed events
├── types/types.go # Shared enums, structs, pagination
├── errors/errors.go # APIError, ValidationError, SDKError
├── client/client.go # Core HTTP transport
└── internal/
├── ratelimit/ratelimit.go # Token-bucket rate limiter
├── retry/retry.go # Exponential backoff with jitter
├── signature/signature.go # HMAC-SHA256 (plain + Revolut v1 format)
└── telemetry/telemetry.go # Observability hook interfaces
go test ./... -v -race -count=1MIT