Creating a Payment Challenge (Go SDK)
Create an x402 payment challenge as the payee with Client.Charge, specifying the amount as human USDC or raw token base units, then hand the challenge to the payer.
Use Client.Charge when your agent is the payee and needs to request a USDC payment from another agent. It creates an x402 payment challenge that the payer signs and settles with Pay.
Before calling Charge, register a payout address once with RegisterPayoutAddress. Charge resolves pay_to from that registration, so a charge without one fails.
This page covers the payee side (creating the challenge). The payer settles it with client.Pay(ctx, challenge, signer); for the shared payment model, see x402 Payments Overview. For a payment that rides a real email thread instead of a synthetic id, see the email-native flow on the same page.
Construct the x402 client#
Charge is a method on *primitive.X402Client, the x402 payments client built with NewX402Client; client construction and the shared payment model are explained in x402 Payments Overview. With zero options it reads PRIMITIVE_API_KEY from the environment and targets the production host (https://api.primitive.dev).
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"),
})
_, err := client.Charge(ctx, primitive.X402ChargeInput{
AmountUsdc: "0.01",
Network: "base-sepolia",
})
if err != nil {
log.Fatal(err)
}
}
Create the challenge#
- 1
Choose an amount format#
Set exactly one of
AmountUsdc(a human USDC decimal string like"0.01", the documented easy path) orAmount(token base units, e.g."10000"); see x402 Payments Overview for how the two formats relate. - 2
Call Client.Charge#
challenge, err := client.Charge(ctx, primitive.X402ChargeInput{ AmountUsdc: "0.01", // human USDC amount Network: "base-sepolia", PayerOrg: os.Getenv("PAYER_ORG_ID"), // org allowed to pay Description: "API call", }) if err != nil { log.Fatal(err) }Networkis"base-sepolia"(testnet) or"base"(mainnet).PayerOrgbinds the challenge to a specific paying org and is optional.Descriptionis a free-text label surfaced back to the payer. - 3
Hand the challenge to the payer#
challenge(a*X402Challenge) carriespayment_requirementsandnonce_binding, the exact fields the payer'sPaycall signs over. Deliver it over any out-of-band channel (API response, dashboard, message).
Re-hydrate a challenge later#
GetChallenge re-hydrates an existing challenge by id, so you can retry Pay after a process restart without creating a duplicate:
challenge, err := client.GetChallenge(ctx, challengeID)
if err != nil {
log.Fatal(err)
}
Need the challenge to ride a real email thread instead of a synthetic id? Use CreateEmailChallenge from the email-native flow described on x402 Payments Overview instead of Charge.
Setting both AmountUsdc and Amount on the same X402ChargeInput, or neither, is rejected before any network call is made. Set exactly one.
Errors#
Charge returns a *primitive.X402Error on any client-side, transport, or non-2xx server error, the shared x402 error shape described in x402 Payments Overview:
import (
"errors"
"log"
primitive "github.com/primitivedotdev/sdks/sdk-go"
)
challenge, err := client.Charge(ctx, primitive.X402ChargeInput{Amount: "10000"})
if err != nil {
var x402Err *primitive.X402Error
if errors.As(err, &x402Err) {
log.Printf("charge failed: status=%d retryAfter=%v", x402Err.Status, x402Err.RetryAfter)
}
return
}
_ = challenge
Common Charge-time rejections, all local (no network call made):
- Malformed or missing amount:
Amountmust be a positive integer string;AmountUsdcmust be a positive decimal with at most 6 decimal places. - Both
AmountandAmountUsdcset, or neither set.
See x402 Errors for the full status-code reference, including retry-after handling and indeterminate-outcome cases.
Next steps#
Understand the full payout-registration, charge, pay, and spend-policy model shared across SDKs.
Registering a Payout AddressProve control of a wallet and register it before your first Charge call.
x402 ErrorsLook up X402Error status codes and indeterminate-outcome handling.
x402 Spend PolicyGuard outbound payments with caps, an allowlist, and a kill-switch.
Was this page helpful?