x402 Spend Policy (Go SDK)
Guard outbound x402 payments with a paused kill-switch, per-payment and daily USDC caps, and a payee allowlist using the Go SDK's SetSpendPolicy.
What the spend policy guards#
The spend policy is the org-level guardrail on outbound x402 payments: a Paused kill-switch, per-payment and daily caps in token base units (or nil for no cap), and an allowlist of payee orgs (nil means any on-net payee, an empty slice denies all). Set it before you let an agent pay challenges unattended, so a bug or a compromised key can't spend beyond what you've bounded.
Read it with client.GetSpendPolicy(ctx) and change it with client.SetSpendPolicy(ctx, update). Payout registration is covered in Registering a Payout Address and the charge/pay flow in Creating a Payment Challenge; this page is only about the guardrail that sits in front of Pay.
Read the current policy#
GetSpendPolicy fetches the org's current policy over HTTP and returns the paused flag, both caps, and the allowlist.
- 1
Construct the x402 client#
package main import ( "context" "log" "os" primitive "github.com/primitivedotdev/sdks/sdk-go" ) func main() { client := primitive.NewX402Client(primitive.X402ClientOptions{ APIKey: os.Getenv("PRIMITIVE_API_KEY"), }) ctx := context.Background() policy, err := client.GetSpendPolicy(ctx) if err != nil { log.Fatal(err) } log.Println(policy) }NewX402Clientwith zero options readsPRIMITIVE_API_KEYfrom the environment and targets the production host, same as any other x402 call. - 2
Inspect the fields#
The returned policy carries:
Field Meaning PausedKill-switch. truerefuses every outbound payment.MaxPerPaymentPer-payment cap in token base units, or nilfor no cap.MaxPerDayDaily cap in token base units, or nilfor no cap.AllowlistPayee org ids allowed to receive payment. nilmeans any on-net payee; an empty slice denies all.USDC has 6 decimals, so a cap of
"5000000"is 5.00 USDC.
Update the policy#
SetSpendPolicy merges: only the fields you set on the X402SpendPolicyUpdate change, and every field you omit keeps its current server-side value. Build the update with its setter methods, which chain.
- 1
Pause outbound payments (kill-switch)#
var update primitive.X402SpendPolicyUpdate update.SetPaused(true) policy, err := client.SetSpendPolicy(ctx, update) if err != nil { log.Fatal(err) } log.Println("paused:", policy.Paused)Nothing else on the policy changes: caps and the allowlist stay exactly as they were before this call.
- 2
Set a per-payment cap#
var update primitive.X402SpendPolicyUpdate update.SetPaused(false).SetMaxPerPayment("5000000") // 5.00 USDC policy, err := client.SetSpendPolicy(ctx, update) if err != nil { log.Fatal(err) }Caps are token base units as decimal strings, the same convention as
Charge'sAmountfield. Chain setters to change several fields in one call. - 3
Clear a cap#
var update primitive.X402SpendPolicyUpdate update.ClearMaxPerPayment() update.ClearMaxPerDay() policy, err := client.SetSpendPolicy(ctx, update) if err != nil { log.Fatal(err) } // policy.MaxPerPayment and policy.MaxPerDay are now nil (no cap).Use
ClearMaxPerPayment/ClearMaxPerDayto remove a cap. Because omitted fields keep their current value, there is no way to clear a cap by leaving it unset.
Paused: true is the kill-switch: while it is set, every outbound payment is refused regardless of caps or allowlist. Flip it back with update.SetPaused(false) when you're ready to resume.
List registered payout addresses#
ListPayoutAddresses returns every payout address registered for the org, including which one is the default per network.
Spend policy governs outbound payment; the payout address directory is where inbound payment lands. List it alongside the policy when auditing an org's payment posture:
addresses, err := client.ListPayoutAddresses(ctx)
if err != nil {
log.Fatal(err)
}
for _, addr := range addresses {
log.Println(addr)
}
Registering a new payout address is covered in Registering a Payout Address.
Errors#
Every spend-policy method returns a *primitive.X402Error on a client-side, transport, or non-2xx server error; use errors.As to inspect it. Check Status (0 means the request never reached the server), Body (the parsed error envelope), and RetryAfter. See x402 Errors for the full error-handling reference.
Next steps#
Prove control of a wallet and register it as your default payout destination before you charge.
Creating a Payment ChallengeCreate an x402 challenge as the payee with Client.Charge.
x402 ErrorsInterpret X402Error status codes, retry-after headers, and indeterminate outcomes.
x402 Payments OverviewUnderstand the full non-custodial payment model shared across every SDK.
Was this page helpful?