Cookbook

Recipes

Worked examples for the patterns that come up most often when building Vincia plugins. Each recipe is short — adapt them, don't copy wholesale.

Idempotency keys

Any side-effecting call your plugin makes should be safe to retry. The simplest mechanism is an idempotency key: a deterministic value derived from the request that the downstream provider uses to deduplicate.

Pattern

async charge(ctx, params) {
  const idem = `${ctx.tenant_id}:${params.order_id}`;
  const res = await ctx.outbound.fetch('https://api.example.com/charge', {
    method: 'POST',
    headers: { 'idempotency-key': idem },
    body: JSON.stringify(params),
  });
  return await res.json();
}

Why

A deterministic idempotency key turns "did this charge twice?" into "no, the provider deduped." Without it you carry the burden in your storage code.

Webhook reconciliation

Most payment providers deliver a webhook callback per state change (charged, refunded, disputed). The wrong pattern is to mutate state from the webhook directly; the right pattern is to use the webhook as a signal and reconcile against the provider's authoritative API.

Pattern

async webhook(ctx, event) {
  // Don't trust the webhook body for state. Use it as a hint.
  const remote = await ctx.outbound.fetch(
    `https://api.example.com/charges/${event.id}`,
    { headers: { authorization: `Bearer ${ctx.secrets.get('api_key')}` } },
  );
  const truth = await remote.json();

  // Now update local storage with the provider's view, not the webhook's.
  await ctx.storage.put('charges', event.id, truth);
}

Why