"No response body" on particular Stripe webhook events

When you start a task under a supervisor, you’re getting better observability (via the observer) and proper graceful shutdown - when you’re shutting down your application (e.g. because you’re doing a deployment of a new version), the application will wait until the task completes. The docs for Task.start:

If the current node is shutdown, the node will terminate even if the task was not completed. For this reason, we recommend to use Task.Supervisor.start_child/2 instead, which allows you to control the shutdown time via the :shutdown option.

With just Task.start, the VM shutdown will just kill the task immediately, which means you might “loose” the webhook (Stripe won’t retry as you’ve confirmed it’s been accepted).

Moving to a Task.Supervisor is pretty straightforward:

  • Add {Task.Supervisor, name: MyApp.TaskSupervisor} to your children in MyApp.Application,
  • Start tasks under the supervisor with Task.Supervisor.start_child:
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn ->
  IO.puts "I am running in a task"
end)

start_child seems to be what you need in your use case, but see the docs if you need to await for the result of the task.

The rule of thumb is that no task should run unsupervised (if the task completes before the code that invokes it, you should be fine with using them directly though).

But all of this won’t protect you from the VM being insta-killed from the outside (e.g. OOM killer or a deployment process that doesn’t do proper app termination) - you’re still susceptible to loosing a webhook/task in the middle of processing. The answer to that would be to use persistent background jobs for processing. Oban is an amazing tool for that - you would enqueue a job in the controller (which is really fast) and the processing will happen in the background and you get access to all the features (retries, backoff, concurrency control, monitoring, etc.) as a bonus.

5 Likes