Skip to content

Repository files navigation

revolut-go

A production-grade, idiomatic Go SDK for the complete Revolut Developer API platform.

Go 1.25+ Zero external dependencies Tests License: MIT


APIs covered

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


Design principles

  • 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 WithXxx options on all clients
  • Generics for paginationtypes.PageResponse[T] across all paginated endpoints
  • Structured errors*APIError, *ValidationError, *SDKError with errors.As support
  • 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

Installation

go get github.com/iamkanishka/revolut-go

Quick start

Unified SDK

package 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)
}

Sandbox

sdk, err := revolut.New(
    revolut.WithMerchantKey("sk_sandbox_..."),
    revolut.WithSandbox(),
)

Error handling

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 */ }
        }
    }
}

Webhooks

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)

Subscriptions (new Variations + Phases model)

// 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})

Fast Checkout address validation

// 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)

Business API webhooks v2 with secret rotation

// 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})

Incremental authorisation (pre-auth orders)

// 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 webhook

Pagination

req := 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
}

Retry & rate limiting

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),
)

Telemetry / observability

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 */ },
    }),
)

Package structure

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

Running tests

go test ./... -v -race -count=1

License

MIT

About

A production-grade, idiomatic Go SDK for the complete Revolut Developer API platform

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages