x402 Errors
Reference for the Go SDK's *primitive.X402Error type: how to inspect its Status, Body, and RetryAfter fields, and how to handle a Status 0 error where the payment outcome is indeterminate.
What error type x402 methods return#
Every method on primitive.X402Client returns a *primitive.X402Error on a client-side, transport, or non-2xx server error. Use errors.As to pull it out of the returned error:
import (
"errors"
"fmt"
primitive "github.com/primitivedotdev/sdks/sdk-go"
)
receipt, err := client.Pay(ctx, challenge, payer)
if err != nil {
var x402Err *primitive.X402Error
if errors.As(err, &x402Err) {
fmt.Println(x402Err.Status, x402Err.Message)
}
return err
}
This covers Charge, Pay, RegisterPayoutAddress, GetChallenge, ListPayoutAddresses, SetSpendPolicy, CreateEmailChallenge, and the other X402Client methods described in x402 Payments Overview, Registering a Payout Address, Creating a Payment Challenge, and x402 Spend Policy.
X402Error fields#
| Field | Type | Description |
|---|---|---|
Status | int | The HTTP status code, or 0 for a client-side/transport error that never reached the server. |
Message | string | Human-readable error message (from Error()). |
Body | parsed error envelope or nil | The parsed error response body, when the server returned one. |
RetryAfter | pointer or nil | The Retry-After response header value, when the server sent one (e.g. on a 429). |
Error() returns Message, so *primitive.X402Error satisfies the standard error interface directly.
Status 0: the request may never have been sent#
A Status of 0 means the error is client-side or transport-level: DNS failure, connection refused, TLS error, a client-side timeout, or any other failure before a response was received.
On Pay, a Status == 0 error means the request may not have been sent, so the payment outcome is indeterminate. Do not assume the payment failed and retry blindly, since the original request may have reached the server and be settling.
Recovery for a Status == 0 error on Pay:
- Call
client.GetChallenge(ctx, id)to re-hydrate the challenge and check its state before retryingPay. - If the challenge shows as already paid or settled, do not resubmit.
- If the challenge is still open, retry
Paywith the re-hydrated challenge.GetChallengeexists for exactly this: re-hydrating a challenge by id to retryPay, for example after a restart.
Retry-After#
When the server returns a 429 (or any response carrying a Retry-After header), that value is available on RetryAfter:
var x402Err *primitive.X402Error
if errors.As(err, &x402Err) && x402Err.RetryAfter != nil {
time.Sleep(time.Duration(*x402Err.RetryAfter) * time.Second)
// retry
}
Non-2xx server errors#
For any non-2xx response the server did return, Body carries the parsed error envelope (when the response body was JSON) so you can inspect the server's error code/message beyond Message.
receipt, err := client.Pay(ctx, challenge, payer)
if err != nil {
var x402Err *primitive.X402Error
if errors.As(err, &x402Err) {
switch {
case x402Err.Status == 0:
// transport-level; outcome indeterminate, see above
case x402Err.Status == 429:
// rate limited; consult x402Err.RetryAfter
case x402Err.Status >= 400 && x402Err.Status < 500:
// rejected by the server (e.g. payment_declined, validation error)
case x402Err.Status >= 500:
// server error; safe to retry
}
}
}
Signing and validation errors#
Methods that validate input locally before making a network call (for example, a malformed challenge passed to Pay, or an invalid signer) also return *primitive.X402Error with Status: 0, so the same errors.As pattern catches both "never reached the server" transport failures and "rejected before any request was made" validation failures. Check Message to distinguish them.
Next steps#
Create an x402 challenge as the payee with Client.Charge.
x402 Signing PrimitivesDrive nonce derivation and signing yourself when Pay doesn't fit your flow.
x402 Payments OverviewUnderstand the non-custodial x402 payment model shared across SDKs.
Error HandlingInspect the Go SDK's other error types: APIError, PrimitiveWebhookError, and more.
Was this page helpful?