Go SDK
The official XRNotify Go SDK provides an idiomatic, context-aware client for managing webhooks, querying deliveries, and verifying signatures. Requires Go 1.21 or later. All methods accept a context.Context for cancellation and deadline propagation.
Installation
go get github.com/xrnotify/xrnotify-go
Client setup
Create a client with your API key. The client is safe for concurrent use across goroutines.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/xrnotify/xrnotify-go"
)
func main() {
client := xrnotify.NewClient(os.Getenv("XRNOTIFY_API_KEY"))
// Optional: use test environment
// client := xrnotify.NewClient(
// os.Getenv("XRNOTIFY_TEST_KEY"),
// xrnotify.WithEnvironment(xrnotify.EnvironmentTest),
// )
ctx := context.Background()
_ = client
_ = ctx
}Thread safety: A single *xrnotify.Client can be shared across goroutines. Create one at startup and reuse it throughout the lifetime of your application.
Creating a webhook
Register a webhook endpoint. The returned struct includes Secret — this field is only populated on the creation response.
webhook, err := client.Webhooks.Create(ctx, &xrnotify.CreateWebhookParams{
URL: "https://yourapp.com/webhooks/xrpl",
EventTypes: []string{"payment.xrp", "nft.minted"},
AccountFilters: []string{"rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"},
Description: "Payment processor",
})
if err != nil {
log.Fatalf("create webhook: %v", err)
}
fmt.Println("ID:", webhook.ID)
fmt.Println("Secret:", webhook.Secret) // Store immediately — only available onceImportant: webhook.Secret is only set in the creation response. Subsequent fetches of the same webhook will return an empty string for this field.
Listing and managing webhooks
List, update, and delete webhooks. The list method returns a paginated result; use result.HasMore and StartingAfter to page through results.
// List webhooks
result, err := client.Webhooks.List(ctx, &xrnotify.ListWebhooksParams{
Limit: 10,
})
if err != nil {
log.Fatal(err)
}
for _, wh := range result.Data {
fmt.Println(wh.ID, wh.URL, wh.IsActive)
}
// Paginate
if result.HasMore {
lastID := result.Data[len(result.Data)-1].ID
nextPage, _ := client.Webhooks.List(ctx, &xrnotify.ListWebhooksParams{
Limit: 10,
StartingAfter: lastID,
})
_ = nextPage
}
// Update a webhook
active := false
_, err = client.Webhooks.Update(ctx, "wh_abc123", &xrnotify.UpdateWebhookParams{
IsActive: &active,
})
// Delete a webhook
err = client.Webhooks.Delete(ctx, "wh_abc123")Listing deliveries
import "time"
since := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
deliveries, err := client.Deliveries.List(ctx, &xrnotify.ListDeliveriesParams{
WebhookID: "wh_abc123",
Status: "failed",
Since: &since,
})
if err != nil {
log.Fatal(err)
}
for _, d := range deliveries.Data {
fmt.Println(d.ID, d.EventType, d.StatusCode, d.LastError)
}Verifying signatures
Use the verify sub-package to validate incoming webhook requests. The function uses hmac.Equal for constant-time comparison.
package main
import (
"encoding/json"
"io"
"log"
"net/http"
"os"
"github.com/xrnotify/xrnotify-go"
"github.com/xrnotify/xrnotify-go/verify"
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
ok := verify.Signature(
body,
r.Header.Get("X-XRNotify-Signature"),
os.Getenv("WEBHOOK_SECRET"),
)
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
var event xrnotify.Event
if err := json.Unmarshal(body, &event); err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
log.Printf("Received event: %s in ledger %d", event.EventType, event.LedgerIndex)
switch event.EventType {
case "payment.xrp":
// unmarshal event.Payload into xrnotify.PaymentXrpPayload
case "nft.minted":
// unmarshal event.Payload into xrnotify.NftMintedPayload
}
w.WriteHeader(http.StatusOK)
}
func main() {
http.HandleFunc("/webhooks/xrpl", webhookHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}Raw body required: Read the body with io.ReadAll before calling verify.Signature. Do not use a JSON decoder directly on r.Body before verification.
Struct types
The SDK provides typed structs for all event envelopes and payloads. The Payload field is a json.RawMessage so you can unmarshal it into the appropriate typed struct based on EventType.
// Event envelope — same structure for all event types
type Event struct {
EventID string `json:"event_id"`
EventType string `json:"event_type"`
LedgerIndex int64 `json:"ledger_index"`
Timestamp time.Time `json:"timestamp"`
Network string `json:"network"`
WebhookID string `json:"webhook_id"`
Payload json.RawMessage `json:"payload"`
}
// Typed payload structs
type PaymentXrpPayload struct {
Sender string `json:"sender"`
Receiver string `json:"receiver"`
Amount string `json:"amount"`
AmountXRP string `json:"amount_xrp"`
Fee string `json:"fee"`
DeliveredAmount string `json:"delivered_amount"`
DestinationTag *int64 `json:"destination_tag"`
SourceTag *int64 `json:"source_tag"`
TxHash string `json:"tx_hash"`
LedgerIndex int64 `json:"ledger_index"`
Sequence int64 `json:"sequence"`
}