Registering a Payout Address
Prove control of a wallet with a local ownership signature and register it as your org's default x402 payout destination on a given network, a one-time step before you can call Charge.
Register a payout address once, per network, before you call Charge. The registration proves you control a wallet by signing an ownership message locally with your private key; the recovered address becomes the pay_to destination Charge resolves automatically for every future challenge on that network.
Do this the first time you set up payments as a payee, and again whenever you want to change which wallet receives funds on a given network. You don't need it to pay a challenge, only to receive.
x402 payments are non-custodial: your private key never leaves your machine, and Primitive never holds funds. For the full payment model (charge → pay → settle) and terminology, see x402 Payments Overview.
What the registration proves#
Registration proves you control the wallet: it sends a locally signed ownership message, never your private key.
RegisterPayoutAddress posts that signature alongside the address. The message is built by BuildPayoutRegistrationMessage(org, address, network, issuedAt) and must be byte-identical to what the platform recomputes:
Primitive x402 payout address authorization
I authorize this address as a payout destination for my Primitive organization.
org: <org-id>
address: <lowercased address>
network: <network>
issued: <issued-at>
Your organization id is embedded in the signed bytes. That means a captured signature can never be replayed to register the address under a different org. The org id is resolved automatically from your API key, so you never set it yourself; the X402ClientOptions.APIKey you construct the client with determines it.
Prerequisites#
You need a Primitive API key, a wallet private key in an environment variable, and the Go SDK installed.
- A Primitive API key (
prim_testin examples, or your production key viaPRIMITIVE_API_KEY). - A wallet private key for the address you want paid, in an environment variable such as
PAYEE_KEY. Never hardcode it. - The Go SDK installed:
go get github.com/primitivedotdev/sdks/sdk-go@latest.
- 1
Construct the x402 client#
Build a client from your API key. With zero options it reads
PRIMITIVE_API_KEYfrom the environment and targets the production host.package main import ( "context" "log" "os" primitive "github.com/primitivedotdev/sdks/sdk-go" ) func main() { ctx := context.Background() client := primitive.NewX402Client(primitive.X402ClientOptions{ APIKey: os.Getenv("PRIMITIVE_API_KEY"), }) _ = ctx _ = client } - 2
Build a signer from your private key#
NewPrivateKeySignerholds the wallet key in process memory and never sends it to Primitive. It signs both the EIP-712 payment authorization (used byPay) and the personal-sign ownership message (used here).payee, err := primitive.NewPrivateKeySigner(os.Getenv("PAYEE_KEY")) if err != nil { log.Fatal(err) } - 3
Register the address#
Call
RegisterPayoutAddresswith the target network and, optionally, a human-readable label. LeaveOrgunset unless you need to override the organization resolved from your API key.label := "treasury" result, err := client.RegisterPayoutAddress(ctx, primitive.X402PayoutRegistrationInput{ Network: "base-sepolia", Label: &label, }, payee) if err != nil { log.Fatal(err) } - 4
Verify it landed#
List your registered payout addresses to confirm the new default:
addresses, err := client.ListPayoutAddresses(ctx) if err != nil { log.Fatal(err) } for _, a := range addresses { log.Println(a) }A successful registration means later calls to
Chargeon that network resolvePayToto this address automatically; you never pass a payout address onChargeitself.
Networks#
Two networks are supported: "base-sepolia" (testnet, used in these examples) and "base" (mainnet). Register separately per network; a registration on one network does not carry over to the other.
Registering a new default address for a network replaces the previous one for future challenges. Any challenge already created before the change keeps its original pay_to.
You only register a payout address as the payee. If you're the payer signing and settling someone else's challenge, skip this step entirely; see x402 Payments Overview for the payer side of the flow.
Errors#
Every method on X402Client, including RegisterPayoutAddress, returns a *primitive.X402Error on a client-side, transport, or non-2xx server error. Use errors.As to inspect it:
import (
"errors"
"log"
primitive "github.com/primitivedotdev/sdks/sdk-go"
)
var xerr *primitive.X402Error
if errors.As(err, &xerr) {
log.Printf("status=%d retryAfter=%v body=%v", xerr.Status, xerr.RetryAfter, xerr.Body)
}
Status is 0 when the request never reached the server (a DNS failure, a connection error, or a client-side timeout), distinct from a 4xx/5xx the server actually returned. See x402 Errors for the full breakdown of status codes and retry semantics.
Next steps#
Use your registered address as the payee and request a USDC payment with Client.Charge.
x402 Signing PrimitivesDrive nonce derivation and EIP-712 signing yourself if Pay/RegisterPayoutAddress doesn't fit your signing flow.
x402 Spend PolicyGuard outbound payments with caps, an allowlist, and a kill-switch once payments are flowing.
x402 ErrorsInterpret X402Error status codes and indeterminate-outcome cases in detail.
Was this page helpful?