# Rollbacks

> Run a fallback or compensating action after a step exhausts its retries.

If charging an order still fails after retries, release its inventory reservation or choose another payment path. Catch the failed `step.run()` error and put the fallback or compensation in its own step.

## Add a compensating step

This TypeScript v4 example reserves inventory, tries to charge an order, and releases the reservation if charging still fails. The three called functions represent your application's integrations.
It uses the `inngest` client from the [Quick start](/docs-markdown/durable-execution/quick-start).

```typescript {{ title: "TypeScript" }}
export const fulfillOrder = inngest.createFunction(
  {
    id: "fulfill-order",
    triggers: { event: "shop/order.placed" },
  },
  async ({ event, step }) => {
    const orderId = event.data.orderId;
    const reservationId = await step.run("reserve-inventory", () =>
      reserveInventory(orderId)
    );

    try {
      await step.run("charge-order", () => chargeOrder(orderId));
    } catch (error) {
      await step.run("release-inventory", () =>
        releaseInventory(reservationId)
      );
      throw error;
    }
  }
);
```

```python {{ title: "Python" }}
import inngest

@inngest_client.create_function(
    fn_id="fulfill-order",
    trigger=inngest.TriggerEvent(event="shop/order.placed"),
)
async def fulfill_order(ctx: inngest.Context) -> None:
    order_id = ctx.event.data["orderId"]

    async def reserve() -> str:
        return await reserve_inventory(order_id)

    reservation_id = await ctx.step.run("reserve-inventory", reserve)

    async def charge() -> None:
        await charge_order(order_id)

    async def release() -> None:
        await release_inventory(reservation_id)

    try:
        await ctx.step.run("charge-order", charge)
    except inngest.StepError:
        await ctx.step.run("release-inventory", release)
        raise
```

```go {{ title: "Go" }}
import (
	"context"

	"github.com/inngest/inngestgo"
	"github.com/inngest/inngestgo/step"
)

type OrderPlacedData struct {
	OrderID string `json:"orderId"`
}

func FulfillOrder(client inngestgo.Client) (inngestgo.ServableFunction, error) {
	return inngestgo.CreateFunction(
		client,
		inngestgo.FunctionOpts{ID: "fulfill-order"},
		inngestgo.EventTrigger("shop/order.placed", nil),
		func(ctx context.Context, input inngestgo.Input[OrderPlacedData]) (any, error) {
			orderID := input.Event.Data.OrderID

			reservationID, err := step.Run(ctx, "reserve-inventory", func(ctx context.Context) (string, error) {
				return reserveInventory(ctx, orderID)
			})
			if err != nil {
				return nil, err
			}

			_, err = step.Run(ctx, "charge-order", func(ctx context.Context) (any, error) {
				return nil, chargeOrder(ctx, orderID)
			})
			if err != nil {
				// charge-order exhausted its retries: compensate in its own step.
				_, releaseErr := step.Run(ctx, "release-inventory", func(ctx context.Context) (any, error) {
					return nil, releaseInventory(ctx, reservationID)
				})
				if releaseErr != nil {
					return nil, releaseErr
				}
				return nil, err
			}

			return nil, nil
		},
	)
}
```

Inngest retries `charge-order` before the `catch` block runs. If it still fails, `release-inventory` is its own durable, retriable step. Rethrowing the error leaves the function failed after compensation; returning instead would let the function continue. Give reservation, charge, and release operations stable IDs so each can be called again safely.

## Choose recovery or compensation

- **Fallback.** Call another service in a new step, then continue when its result satisfies the workflow.
- **Compensation.** Undo a completed external action in a new step, such as releasing a reservation. This is a business action, not an automatic transaction rollback.
- **Ignore a noncritical failure.** Catch the failed step and record the outcome so the function can continue intentionally.
  Catch only a step whose failure you can handle. An unhandled failed step makes the function fail. A function-level [Failure handlers](/docs-markdown/durable-execution/guides-and-advanced/error-handling/failure-handlers) runs after the function has exhausted retries; it serves a different purpose from local recovery.