Documentation Index: Fetch llms.txt first to discover every published page. This page is also available as Markdown at /go-error-handling.md.
Verified · 8/11/2026

Error Handling

Reference for every error type the Go SDK raises, APIError, PrimitiveWebhookError, WebhookVerificationError, WebhookPayloadError, WebhookValidationError, and RawEmailDecodeError, and how to inspect each one.

APIError#

*primitive.APIError is returned by Client.Send, Client.Reply, Client.Forward, and Client.SemanticSearch on any non-2xx response from the Primitive API.

type APIError struct {
	StatusCode int
	Code       string
	Message    string
	RetryAfter *int
	Gates      []primitiveapi.GateDenial
	RequestID  string
	Details    *primitiveapi.ErrorResponseErrorDetails
	Payload    any
}

func (e *APIError) Error() string
FieldTypeDescription
StatusCodeintThe HTTP status code (400, 401, 403, 404, 422, 429, 500, 502, 503).
CodestringThe server's machine-readable error code (e.g. validation_error, recipient_not_allowed, inbound_not_repliable).
MessagestringHuman-readable error message. Returned verbatim by Error().
RetryAfter*intSeconds to wait before retrying, populated only on 429 responses that carry a Retry-After header. nil otherwise.
Gates[]primitiveapi.GateDenialPresent when a send/reply was denied by a policy gate (e.g. sending to an unconfirmed recipient). Empty when not applicable.
RequestIDstringThe server's request id, when present in the error envelope. Useful when filing a support request.
Details*primitiveapi.ErrorResponseErrorDetailsAdditional structured error detail, when the server includes it.
PayloadanyThe raw decoded error response, for cases not covered by the typed fields above.
import (
	"errors"
	"log"
	"time"

	primitive "github.com/primitivedotdev/sdks/sdk-go"
)

result, err := client.Send(ctx, primitive.SendParams{
	From:    "Support <support@example.com>",
	To:      "alice@example.com",
	Subject: "Hello",
	BodyText: "Hi there",
})
if err != nil {
	var apiErr *primitive.APIError
	if errors.As(err, &apiErr) {
		switch apiErr.StatusCode {
		case 429:
			if apiErr.RetryAfter != nil {
				time.Sleep(time.Duration(*apiErr.RetryAfter) * time.Second)
			}
		case 422:
			log.Printf("send rejected: %s (%s)", apiErr.Message, apiErr.Code)
		default:
			log.Printf("send failed: %s", apiErr.Error())
		}
	}
}
Note

Client.Reply returns inbound_not_repliable (Code, HTTP 422) when the inbound row is not in a state that can be replied to: it was rejected at ingestion, its content was discarded, or it has no recipient recorded. A missing Message-Id does not trigger this error; it only omits the threading headers.

A canceled or timed-out context.Context surfaces as context.Canceled or context.DeadlineExceeded, not as *primitive.APIError. Check for these separately to distinguish a client-side abort from a server response, see Context, Timeouts, and Cancellation.

Webhook errors#

Five error types cover webhook verification, parsing, and validation failures, each carrying a stable Code you can branch on.

PrimitiveWebhookError#

The shared webhook error type the SDK exposes alongside the specific verification, payload, validation, and raw-email variants.

WebhookVerificationError#

Raised when signature verification fails: bad HMAC, malformed header, expired timestamp, or a missing secret.

CodeCause
MISSING_SECRETThe webhook secret argument was empty or not provided.
INVALID_SIGNATURE_HEADERThe Primitive-Signature (or Standard Webhooks) header is missing or malformed.
TIMESTAMP_OUT_OF_RANGEThe signed timestamp is older than the tolerance window (default 300s) or too far in the future.
SIGNATURE_MISMATCHThe computed HMAC does not match any signature in the header. Usually caused by verifying a re-serialized body instead of the raw request bytes.
import (
	"errors"
	"log"
	"os"

	primitive "github.com/primitivedotdev/sdks/sdk-go"
)

event, err := primitive.HandleWebhookEvent(primitive.HandleWebhookOptions{
	Body:    rawBody,
	Headers: req.Header,
	Secret:  os.Getenv("PRIMITIVE_WEBHOOK_SECRET"),
})
if err != nil {
	var verifyErr *primitive.WebhookVerificationError
	if errors.As(err, &verifyErr) {
		log.Printf("signature verification failed: %s", verifyErr.Error())
		// respond 400, do not process the payload
	}
}

WebhookPayloadError#

Raised when the raw request body cannot be turned into a JSON object at all, before any schema validation runs.

CodeCause
PAYLOAD_NULLThe body decoded to null.
PAYLOAD_IS_ARRAYThe body is a JSON array instead of an object.
PAYLOAD_EMPTY_BODYThe request body was empty.
JSON_PARSE_FAILEDThe body is not valid JSON.
PAYLOAD_MISSING_EVENTNo X-Webhook-Event header and no event field in the body, so the event family cannot be classified.
PAYLOAD_WRONG_TYPEA required field is missing or has the wrong type (used by the raw-email and download helpers, e.g. email.content.download.expires_at).
PAYLOAD_UNKNOWN_EVENTHandleWebhook received a known, verified event that is not email.received (it is hard-typed to that event only).

WebhookValidationError#

Raised when a payload parses as JSON but fails schema validation against the canonical email.received shape, or when ValidateEmailAuth is given a malformed auth object.

RawEmailDecodeError#

Raised by DecodeRawEmail and VerifyRawEmailDownload when the raw MIME bytes cannot be decoded or verified.

CodeCause
NOT_INCLUDEDThe raw email was not included inline; the caller must download it from email.content.download.url instead.
INVALID_BASE64The inline email.content.raw.data field is not valid base64.
HASH_MISMATCHThe decoded (or downloaded) bytes' SHA-256 does not match email.content.raw.sha256. Indicates corrupted or tampered content.

See Raw Email and Attachment Downloads for the functions that raise this error.

Distinguishing error types#

Use errors.As to branch on the concrete type:

import (
	"context"
	"errors"

	primitive "github.com/primitivedotdev/sdks/sdk-go"
)

var (
	apiErr        *primitive.APIError
	verifyErr     *primitive.WebhookVerificationError
	payloadErr    *primitive.WebhookPayloadError
	validationErr *primitive.WebhookValidationError
	rawErr        *primitive.RawEmailDecodeError
)

switch {
case errors.As(err, &apiErr):
	// *primitive.APIError — non-2xx API response
case errors.As(err, &verifyErr):
	// *primitive.WebhookVerificationError — bad signature/timestamp
case errors.As(err, &payloadErr):
	// *primitive.WebhookPayloadError — malformed body
case errors.As(err, &validationErr):
	// *primitive.WebhookValidationError — schema mismatch
case errors.As(err, &rawErr):
	// *primitive.RawEmailDecodeError — bad raw-email bytes
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
	// client-side abort, not a server response
}

x402 payment errors (*primitive.X402Error) are a separate type with its own status/retry-after semantics; see x402 Errors.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer