Skip to main content
Every Raise integration eventually encounters errors. Some are transient (gateway timeouts, network blips, brief rate-limit windows) and recover on retry. Some are permanent (validation failures, deleted records, revoked credentials) and require human intervention. The integrations that handle both cases well — distinguishing between them, retrying transient errors appropriately, surfacing permanent ones for review without spamming alerts — are the ones that stay up under production load. This page covers the classification framework, the retry patterns with backoff, circuit breakers for cascading failures, dead-letter queues for permanent failures, and the special considerations for POST /api/Raise/give (which charges payment methods and can’t be retried naively).

Classifying errors

The first and most important decision: is this error transient or permanent? The right response is wildly different. The classifier is the foundation of all retry logic. Get it right, and everything else falls into place. Get it wrong, and you either hammer the API with futile retries or fail to retry transient errors that would succeed.

A reference classifier

JavaScript
This is a starting point; tune it based on what your integration actually encounters. Log unexpected classifications so you can refine the rules.

Retry patterns for transient errors

Transient errors are the bread-and-butter case for retry logic. The patterns:

Exponential backoff

The basic pattern: each retry waits longer than the previous, eventually giving up.
JavaScript
Three things this pattern gets right:
  • Exponential growth means most retries happen quickly, but persistent failures don’t loop tightly.
  • Jitter prevents synchronized retries across many integrations from hammering the API simultaneously.
  • Bounded attempts ensure the retry loop terminates rather than retrying forever.

Honor Retry-After when present

For rate-limit responses, the server may include a Retry-After header indicating how long to wait. Honor it instead of computing a backoff:
JavaScript
The Raise OpenAPI spec doesn’t explicitly document the Retry-After header on rate-limit responses. The pattern assumes it’s present (following common HTTP convention) and falls back to exponential backoff if not. See Rate Limits for what’s known.

Don’t retry forever

A 5xx that persists for hours isn’t transient — it’s a sustained issue worth surfacing for human review. Bound retries at a reasonable number (typically 5 attempts) and move to a different handling strategy after that. See Dead-letter queues below.

Errors that should never be retried

Three classes of errors that retry only makes worse:

401 Unauthorized: the credential is bad

A 401 means the token is invalid, expired, or revoked. Retrying produces the same 401 indefinitely. The fix isn’t a retry — it’s a credential refresh.
JavaScript
Pause the customer’s sync work until a human refreshes the token. Continuing to attempt requests with a bad token just generates noise.

400 validation failures: the request is bad

A 400 typically means the request body has a validation issue — a missing required field, an invalid value, a malformed structure. Retrying with the same body produces the same 400. The fix is to correct the request:
JavaScript
For partner integrations submitting donations, a 400 from POST /api/Raise/give may also indicate a payment failure (card declined, gateway rejection). These also should not be retried with the same payment method — surface them to the donor for a different card.

404 Not Found: the resource doesn’t exist

A 404 from GET /api/Donor/12345 means donor 12345 doesn’t exist (or was deleted). No retry will make it appear. The fix is to handle the absence gracefully:
JavaScript
Sometimes a 404 is expected (the integration was checking for existence). Sometimes it indicates a deeper issue (the donor was deleted between the integration learning about them and the lookup). Don’t retry; the right path depends on context.

Special case: POST /api/Raise/give retries

Donation submissions deserve special attention because they charge payment methods. A naive retry on a network error can produce double charges if the original request succeeded but the response didn’t reach the integration.

The double-charge risk

The integration sees one successful response. The donor sees two charges. The customer has to issue a refund for one of them. Avoid this.

The defensive pattern

For POST /api/Raise/give specifically, never retry network errors blindly. Instead:
JavaScript

Reconciling uncertain donations

When a network error leaves the outcome uncertain, the integration shouldn’t auto-retry. Instead, surface the uncertain donation for reconciliation:
JavaScript
Run this on a short cadence (every few minutes) to resolve uncertain attempts. Only after confirming the original attempt didn’t go through is it safe to retry.
This is the recommended pattern only because the Raise spec doesn’t currently document an idempotency-key header that would solve the problem more elegantly. When such a header becomes available, use it instead — it’s a more robust solution than client-side reconciliation.⚠️ Spec gap: No Idempotency-Key header is documented for POST /api/Raise/give. Confirm whether the platform supports one before relying on the client-side reconciliation pattern.

Circuit breakers

For workloads that touch many records, a sustained failure can produce a cascade — many in-flight requests all hitting the same issue, all retrying, all eventually failing. A circuit breaker stops the cascade by short-circuiting requests after a threshold of failures.

A basic circuit breaker

JavaScript
Use one circuit breaker per logical operation (per-endpoint, per-customer, or per-destination) so a failure in one doesn’t disrupt others:
JavaScript
When the breaker opens, in-flight requests fail fast rather than producing further retries. After the reset timeout, the breaker tentatively allows a few requests through (“half-open”). If they succeed, the breaker closes; if not, it stays open.

When to use circuit breakers

For partner integrations operating at scale (hundreds of customers, thousands of requests per minute), circuit breakers prevent localized issues from cascading into widespread degradation.

Dead-letter queues

When all retries fail, the operation can’t continue. Two options: drop it silently (bad — lost work) or move it to a dead-letter queue for human review (good).

A dead-letter pattern

The flow:
1

Operation fails permanently or exhausts retries

A 400 validation error, a sustained 5xx, or a network error that doesn’t recover.
2

Move the operation to the dead-letter queue

Capture the full operation payload, the last error, and the attempt history.
3

Continue processing other operations

One bad operation doesn’t block the queue.
4

Surface the dead-letter entry for review

Alert or daily digest to ops; expose in a UI for support staff.
5

Investigate and resolve

Either fix the underlying issue and replay, or mark the operation as permanently lost.

Replaying from the dead-letter queue

For operations that failed due to a transient issue that’s now resolved, replay them:
JavaScript
A reasonable UI: an ops dashboard showing dead-letter entries with “replay” and “mark resolved” buttons. Most entries are resolved by replay once the underlying issue is fixed (credential renewed, downstream system back up, etc.).

Surfacing errors to humans

Not every error needs to wake someone up. A reasonable severity model: The right thresholds depend on the integration’s SLA. For a major-donor-focused integration, even a single failed POST /api/Raise/give may warrant a same-day investigation. For a low-priority analytics sync, the same failure might be a warning aggregated into a daily digest.

Useful alert content

A useful error alert includes:
  • The customer affected
  • The operation that failed
  • The error classification (transient, permanent, uncertain)
  • The number of attempts made
  • The last error message
  • A link to the dead-letter entry (or wherever the operation can be inspected and replayed)
  • Suggested next steps based on the error type
JavaScript

Idempotency: the underlying defense

Most error-recovery patterns rely on idempotency — the property that repeating an operation produces the same outcome as running it once. Build idempotency into operations from the start.

Webhook handlers

See Idempotency and Safe Reprocessing for the full pattern. Summary: every event has a unique key (typically contextId + eventType + modifiedDate), and the dedup store records processed events. Retried deliveries skip re-processing.

Downstream writes

For partner integrations writing to external systems, use the external system’s idempotency mechanisms:
  • Database upserts keyed by Raise resource IDs.
  • Idempotency keys on third-party API calls (Stripe, Slack, many modern APIs support them).
  • Conditional logic that checks for existing records before creating new ones.
The combination of webhook-level dedup and downstream-write idempotency ensures retries are safe to perform.

POST /api/Raise/give — the special case

Donation submissions are the most challenging case because the operation is genuinely not idempotent at the API level (no documented idempotency-key header). The client-side reconciliation pattern (see The defensive pattern above) is the workaround. When an idempotency-key header becomes available, switch to it.

A complete error-handling pipeline

Putting the patterns together:
JavaScript
Use this pattern for every Raise API call. The cost of writing the pattern once is small; the cost of not having it is paid at every incident.

A recovery checklist

When designing a Raise integration, walk through these questions:
  • Every API call goes through a function that classifies and retries appropriately
  • POST /api/Raise/give uses the client-side reconciliation pattern, not naive retry
  • Webhook handlers are idempotent — retries don’t produce duplicate side effects
  • Bulk operations use circuit breakers to prevent cascade failures
  • Permanently-failed operations go to a dead-letter queue
  • Dead-letter entries are surfaced to ops with enough context to investigate
  • 401 responses pause the affected customer’s work and alert ops, rather than retrying
  • Network errors and 5xx responses retry with exponential backoff + jitter
  • Retry-After headers are honored when present
  • Rate-limit 429 responses are visible in metrics
Most of these are small individually. Together, they make the difference between an integration that requires constant manual intervention and one that recovers from most failures on its own.

Where to go next

Sync Architecture Patterns

The architectural patterns these error-recovery patterns plug into.

API Performance Tips

Performance patterns complementary to recovery — fewer requests means fewer chances to fail.

Rate Limits

The 429 patterns referenced throughout this page.

Idempotency and Safe Reprocessing

Webhook-specific idempotency that pairs with API error recovery.
Last modified on May 21, 2026