FinaDOCS

Delivery Confirmation Webhook

With confirmation_type SMS_LINK, Confirm Delivery leaves the order OUT_FOR_DELIVERY until the buyer confirms receipt via the SMS link. This webhook is the signed server-to-server signal that the delivery completed — the order moves to DELIVERED and invoicing proceeds.

Part of the buyer confirmation flow

Until the buyer confirms, the order stays OUT_FOR_DELIVERY and invoicing is held. Key your post-delivery bookkeeping off this webhook — there is no browser redirect in this flow, so it is the only completion signal your servers receive.

When it fires

Once per order, when the delivery completes:

  • The buyer confirms via the SMS link FINA sent when Confirm Delivery was called, or
  • a buyer dispute is resolved (approved) by FINA operations — the callback is identical to a direct confirmation.

A raised dispute sends nothing — the webhook only arrives once the dispute is resolved, and invoicing is held in between.

Configure the destination via delivery_callback_webhook_url and delivery_callback_webhook_secret on the Confirm Delivery request. The URL must be HTTPS and its origin allowlisted with FINA at onboarding. If no callback URL was supplied, no webhook is sent — you opted out.

Request & headers

  • POST with Content-Type: application/json.
  • Return 2xx as soon as you have durably accepted the event; do bookkeeping work asynchronously. Anything else counts as a failed delivery and is retried.

Signature headers are present only when a delivery_callback_webhook_secret was supplied:

HeaderDescription
X-Fina-SignatureLowercase-hex HMAC-SHA256 over "<timestamp>.<raw_body>" keyed with your delivery_callback_webhook_secret.
X-Fina-TimestampUnix time (seconds) when the request was signed; also part of the signed string, so it cannot be tampered with.

Body

FieldTypeDescription
order_numberstringFINA's order id (the order_id returned by Initiate Checkout). Also your idempotency key — there is no event_id on this webhook.
statusstringAlways "DELIVERY_CONFIRMED" — the webhook is only sent when the delivery completes.
event_timestampstringRFC 3339 UTC timestamp, e.g. 2026-07-15T06:12:41Z.
Example delivery
{
  "order_number": "F26070910AB3XKQ70",
  "status": "DELIVERY_CONFIRMED",
  "event_timestamp": "2026-07-15T06:12:41Z"
}

Verifying the signature

The scheme is exactly the same as the Down Payment Webhook's — HMAC-SHA256 over the exact raw request bytes, keyed with your delivery_callback_webhook_secret. The samples below work unchanged.

Signature scheme
signed_string = X-Fina-Timestamp + "." + <raw request body bytes>
expected      = hex( HMAC_SHA256( webhook_secret, signed_string ) )
valid         = constant_time_equals( expected, X-Fina-Signature )
const crypto = require("crypto");

// Give your handler the RAW body (e.g. express.raw()), not a re-parsed object.
function verifyFinaWebhook(rawBody, headers, secret) {
  const ts  = headers["x-fina-timestamp"];
  const sig = headers["x-fina-signature"];
  if (!ts || !sig) return false;

  const expected = crypto.createHmac("sha256", secret)
    .update(ts + "." + rawBody)          // rawBody = exact bytes received
    .digest("hex");

  const ok = sig.length === expected.length &&
             crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));

  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300; // replay window
  return ok && fresh;
}

Reliability & idempotency

  • At-least-once delivery. Failed deliveries are retried — the same confirmation can arrive more than once.
  • Idempotency. There is no event_id on this webhook — dedupe on order_number. status is always DELIVERY_CONFIRMED, so a repeat for the same order is always a duplicate.
  • Replay protection. The signature covers X-Fina-Timestamp; reject requests whose timestamp falls outside a freshness window (e.g. ±5 minutes).
  • There is no reconcile-style pull API for delivery state. If a webhook is missed, recover by checking the order against your own records — or contact FINA support to confirm the order's status.