Go
github.com/elyonar/einvoice-go is the official SDK for Go 1.22 and later. It uses the standard library only, takes a context.Context on every call, and returns typed structs with the API’s field names.
Install
go get github.com/elyonar/einvoice-goQuick start
Create one in the Yona app under API keys, with the invoicing preset; it starts with sk_test_. The SDK needs nothing else: no host and no environment setting, because the key chooses the sandbox or the live service.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/elyonar/einvoice-go"
)
func main() {
ctx := context.Background()
client, err := einvoice.New(os.Getenv("YONA_API_KEY"))
if err != nil {
log.Fatal(err)
}
// 1. A buyer
buyer, err := client.Buyers.Create(ctx, &einvoice.CreateBuyerBody{
Name: "Acme Nigeria Ltd",
TaxID: einvoice.Ptr("12345678-0001"),
Email: einvoice.Ptr("accounts@acme.ng"),
PartyType: einvoice.Ptr(einvoice.CreateBuyerDtoPartyTypeCompany),
Address: &einvoice.BuyerAddressDto{
Line1: "1 Marina", City: "Lagos", Country: "NG",
},
})
if err != nil {
log.Fatal(err)
}
// 2. A saved item
item, err := client.Items.Create(ctx, &einvoice.CreateItemBody{
Name: "Laptop",
ItemType: einvoice.CreateItemDtoItemTypeGoods,
HSNCode: einvoice.Ptr("8471.30"),
ProductCategory: "Machinery",
UnitCode: "EA",
UnitPriceMinor: "45000000",
Currency: "NGN",
TaxCategory: "STANDARD_VAT",
})
if err != nil {
log.Fatal(err)
}
// 3. A draft invoice
invoice, err := client.Invoices.Create(ctx, &einvoice.CreateInvoiceBody{
InvoiceKind: "B2B",
InvoiceDate: "2026-10-05",
Currency: "NGN",
BuyerID: einvoice.Ptr(buyer.ID),
LineItems: []einvoice.InvoiceLineItemDto{
{ItemID: einvoice.Ptr(item.ID), Quantity: einvoice.AmountNumber(2)},
},
})
if err != nil {
log.Fatal(err)
}
// 4. Finalise it and report it to the tax authority
if _, err := client.Invoices.Finalise(ctx, invoice.ID); err != nil {
log.Fatal(err)
}
if _, err := client.Submissions.Submit(ctx, invoice.ID, nil); err != nil {
log.Fatal(err)
}
status, err := client.Submissions.GetStatus(ctx, invoice.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println(status.Status)
}Submitting is asynchronous: poll Submissions.GetStatus or listen for the invoice.accepted webhook. The states are explained in Invoice lifecycle.
Every method takes a context.Context first; cancel it or give it a deadline and the request stops.
Sandbox and live
An sk_test_ key works in the sandbox and an sk_live_ key in live, on the same host; client.Mode() tells you which. To make a deployment refuse a key of the wrong kind:
client, err := einvoice.New(key, einvoice.WithAssertMode(einvoice.ModeLive))Responses and pagination
Methods return the API’s answer as a typed struct (invoice.InvoiceNumber). Optional fields are pointers; einvoice.Ptr(v) makes one when building a request. List methods return a page:
query := &einvoice.ListBuyersQuery{Limit: einvoice.Ptr(50.0)}
page, err := client.Buyers.List(ctx, query)page.Data holds the buyers and page.Pagination carries Total, Page, PageSize, TotalPages, HasNext and HasPrevious.
Paginate walks every page for you:
query := &einvoice.ListBuyersQuery{Limit: einvoice.Ptr(100.0)}
list := func(ctx context.Context, page int64) (
*einvoice.Page[einvoice.BuyerViewDto], error,
) {
query.Page = einvoice.Ptr(float64(page))
return client.Buyers.List(ctx, query)
}
err := einvoice.Paginate(ctx, list, func(buyer einvoice.BuyerViewDto) error {
fmt.Println(buyer.Name)
return nil
})Collect takes the same list function and returns every item as one slice.
Received invoices and issued history return *Paginated[D] with the items under Data.Items. PDF downloads return *BinaryResponse with Data, ContentType and FileName. Quantities and prices the API accepts as a string or a number are einvoice.Amount (AmountString("25000.00"), AmountNumber(2)). Two answers come in one of two shapes (IssueCreditNoteData, IssueDebitNoteData); decode them with their As…() methods.
Errors
A refused request returns a typed error you can match with errors.As:
| Status | Type |
|---|---|
| 400, 422 | *ValidationError |
| 401 | *AuthenticationError |
| 402 | *InsufficientCreditsError |
| 403 | *PermissionError |
| 404 | *NotFoundError |
| 409 | *ConflictError |
| 429 | *RateLimitError |
| 5xx | *ServerError |
All of them unwrap to *APIError, which carries Status, ErrorCode, Errors (per field), RequestID and RetryAfter:
_, err := client.Invoices.Create(ctx, params)
var validation *einvoice.ValidationError
var apiErr *einvoice.APIError
switch {
case errors.As(err, &validation):
for _, e := range validation.Errors {
fmt.Println(e.Field, e.Message)
}
case errors.As(err, &apiErr):
fmt.Println(apiErr.Status, apiErr.ErrorCode, apiErr.RequestID)
}Branch on ErrorCode, not on the message, and quote RequestID when you write to support. Problems before the API answers are *TimeoutError, *ConnectionError and *ConfigError; a cancelled context comes back as ctx.Err(). The codes are listed on each page of the API Reference.
Retries and idempotency
Reads, and writes that carry an Idempotency-Key, are retried automatically on network errors, timeouts, 408, 429 and 5xx, with backoff and respecting Retry-After. Other writes are never retried.
Where the API accepts an Idempotency-Key (creating an invoice, submitting, sending, downloading and so on) the SDK generates one per call, so a retry is never applied or charged twice. Pass your own to make a retry safe across restarts:
client.Invoices.Create(ctx, params, einvoice.WithIdempotencyKey("order-"+orderID))Tune the defaults with WithTimeout and WithRetry when creating the client, or per call with WithRequestTimeout, WithRequestHeaders and WithMaxRetries.
Webhooks
http.HandleFunc("/webhooks/yona", func(w http.ResponseWriter, r *http.Request) {
raw, _ := io.ReadAll(r.Body)
secret := os.Getenv("YONA_WEBHOOK_SECRET")
event, err := einvoice.VerifyWebhook(raw, r.Header, secret)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if event.Type == einvoice.WebhookEventInvoiceAccepted {
var data struct{ InvoiceID string `json:"invoiceId"` }
_ = event.DecodeData(&data)
}
w.WriteHeader(http.StatusOK)
})VerifyWebhook checks the Yona-Signature header over the raw body, accepts either signature while a rotated secret overlaps, and returns the event. Always pass the raw bytes, and deduplicate on event.ID. SignWebhookPayload signs a payload the way Yona does, so you can test your handler locally.
Endpoints are created and their secrets rotated in the Yona app; the SDK can list and test endpoints and read and redeliver deliveries and events. See Webhooks for the events.
Configuration
einvoice.New(key, ...) accepts:
| Option | Purpose |
|---|---|
WithAssertMode(ModeSandbox or ModeLive) | refuse a key of the other kind |
WithTimeout(d) | per-attempt timeout (default 30 s) |
WithRetry(RetryConfig{…}) | MaxRetries (2), BaseDelay (500 ms), MaxDelay (8 s), MaxRetryAfter (60 s) |
WithHeaders(map) | headers sent on every request |
WithHTTPClient(*http.Client) | your own client (proxy, TLS) |
WithBaseURL(url) | another gateway, for local development only; the key decides sandbox or live |
What it covers
Every module mirrors a tag of the API Reference, and the method names are the reference’s operation names in the casing of the language (IssueCreditNote, GetStatus). Every method takes ctx first and optional trailing ...RequestOption; request and query types are named after the operation (*CreateInvoiceBody, *ListInvoicesQuery), and a query may be nil. Each method needs the capability shown on the key; the full_integration preset has them all.
| Module | What it does | Reference |
|---|---|---|
client.Invoices, client.Submissions, client.Output, client.ShareLinks | Create, edit and finalise drafts; submit, retry and track them; credit and debit notes; PDFs, sending and share links | Invoices |
client.Buyers | The businesses and people you invoice, with tax-number checks and reachability | Buyers |
client.Items | What you sell, with codes, units, prices and tax categories | Items |
client.Reference | HS codes, reference lists, tax-ID lookup and invoice validation | Reference data |
client.Sellers | Your organisation as the seller, read-only | Sellers |
client.InboundInvoices, client.IssuedHistory | Invoices other businesses sent you, and your issued history retrieved from the authority | Received invoices |
client.Organization, client.InvoiceSettings, client.TaxConnection | Your organisation and its readiness, invoice settings, the tax connection | Organizations, Tax connection |
client.Billing | Credits and usage, payments, statements and your subscription, in Accounts, Payments, Sandbox, Statements, Subscriptions, Transactions | Billing |
client.Webhooks | List and test endpoints; read and redeliver deliveries and events, in Endpoints, Deliveries, Events, EventTypes | Webhooks |
Account management (users, roles, API keys, purchases, webhook endpoint settings) is done in the Yona app, not through the API key.
The source is on GitHub at Elyonar/einvoice-go , with runnable examples under examples/; the package documentation is on pkg.go.dev .
Next steps
- Invoice lifecycle: the states a submission climbs through, and what each one means
- Webhooks: the events, and how deliveries are retried
- Going live: the checklist before you deploy a live key
- API Reference: every field, status code and error code