Skip to main content
Yoshi delivers webhooks with at-least-once semantics. Your endpoint may receive the same event more than once, so design your handler to be idempotent.

Delivery guarantee

AspectBehavior
GuaranteeAt-least-once. Your endpoint may receive duplicate events.
Success criteriaYour server must return a 2xx status code within 15 seconds.
OrderingEvents are not guaranteed to arrive in order. Use created_at for ordering.
BatchingOne HTTP request per event per endpoint. Events are not batched.
Return a 200 response immediately, then process the event asynchronously. If your handler takes longer than 15 seconds, the delivery will be marked as failed and retried.

Retry schedule

When your endpoint returns a non-2xx response or times out, Yoshi retries with exponential backoff:
AttemptDelayTotal elapsed
1st retry5 seconds5 seconds
2nd retry5 minutes~5 minutes
3rd retry30 minutes~35 minutes
4th retry2 hours~2.5 hours
5th retry5 hours~7.5 hours
6th retry10 hours~17.5 hours
7th retry10 hours~27.5 hours
After all retries are exhausted (approximately 28 hours), the event is moved to a dead letter queue.

Endpoint failure handling

If your endpoint consistently fails to respond:
1

Retries exhaust

After all retry attempts fail, the event is marked as failed.
2

Endpoint is disabled

If an endpoint fails repeatedly over multiple events, it is automatically disabled to prevent unnecessary traffic.
3

You investigate and fix

Check the delivery logs via GET /v1/webhooks/deliveries or the consumer portal to see response codes and error details.
4

Re-enable and replay

After fixing the issue, re-enable the endpoint via the API or portal. Use the portal’s replay button to resend failed events.

Idempotency

Every event has a unique id field that is stable across retries. Use this ID to deduplicate events in your handler:
const processedEvents = new Set<string>(); // In production, use Redis or a database

app.post("/webhooks/yoshi", (req, res) => {
  const event = JSON.parse(req.body);

  if (processedEvents.has(event.id)) {
    // Already processed — return 200 to stop retries
    return res.status(200).send("OK");
  }

  processedEvents.add(event.id);

  // Process the event...
  handleEvent(event);
  res.status(200).send("OK");
});
For production use, store processed event IDs in a database or Redis with a TTL of at least 7 days. An in-memory set won’t survive server restarts.

Ordering

Events are not guaranteed to arrive in the order they occurred. For example, you might receive transaction.updated before transaction.created for the same transaction. To handle this:
  • Use the created_at timestamp to determine the chronological order of events.
  • Design your handlers to be order-independent when possible.
  • If you need strict ordering, buffer events and sort by created_at before processing.

Best practices

Your webhook handler should acknowledge receipt as fast as possible. Enqueue the event for background processing rather than doing work inline. This prevents timeouts and retries.
The id field is stable across retries. Store processed event IDs to prevent duplicate processing, especially for events that trigger side effects like sending emails or creating records.
Always verify the webhook signature before trusting the payload. An unverified webhook could be forged by a third party.
Webhook payloads contain resource IDs and metadata, not full objects. Use the IDs to fetch current data from the API. This ensures you always have the latest state, even if events arrive out of order.
Check GET /v1/webhooks/deliveries or the consumer portal regularly to catch failed deliveries early. Set up alerts if your failure rate spikes.
Yoshi may add new event types in the future. If your handler encounters an event type it doesn’t recognize, return 200 and ignore it. Never return an error for unknown types — that would trigger unnecessary retries.
Last modified on April 17, 2026