VendMetric
Request a demo

Pattern

Gate vendor payments on compliance

The point of connecting VendMetric to your systems: stop paying vendors who have let a required document lapse. Here is the pattern, using the read API and webhooks.

The idea

Before your accounts-payable process releases a payment, it asks VendMetric one question: is this vendor compliant right now? If not, the payment is held and the right person is notified, until the vendor fixes what is missing. Removing that check later means changing a financial control, which is exactly the point.

Step 1: check at payment time

When a payment is about to be released, call the compliance endpoint for that vendor and read is_compliant.

async function vendorIsCompliant(vendorId) {
  const res = await fetch(
    `https://app.vendmetric.com/api/v1/vendors/${vendorId}/compliance`,
    { headers: { Authorization: `Bearer ${process.env.VENDMETRIC_KEY}` } },
  );
  if (!res.ok) return { ok: false, reason: "compliance check unavailable" };
  const { data } = await res.json();
  return {
    ok: data.is_compliant,
    reason: data.action_needed.map((r) => r.document_type_name).join(", "),
  };
}

// In your payment release step:
const check = await vendorIsCompliant(vendor.vendmetricId);
if (!check.ok) {
  holdPayment(payment, `Vendor not compliant: ${check.reason}`);
  notifyProcurement(vendor, check.reason);
}

Decide your own policy for the rare case where the check itself is unavailable (hold, or allow with a flag). VendMetric fails loudly with a non-200 rather than pretending a vendor is compliant.

Step 2: release the hold automatically

Rather than re-checking on a timer, subscribe to the vendor.compliance_changed webhook. When a held vendor becomes compliant again, release the payment.

// Your webhook receiver (verify the signature first, see the API reference)
app.post("/webhooks/vendmetric", (req, res) => {
  if (!verify(req.rawBody, req.headers["x-vendmetric-signature"], SECRET)) {
    return res.sendStatus(401);
  }
  const { event, data } = req.body;
  if (event === "vendor.compliance_changed" && data.compliance === "COMPLIANT") {
    releaseHeldPayments(data.vendor_id);
  }
  res.sendStatus(200);
});

A worked example: QuickBooks Online

QuickBooks Online has no single field for "this bill is held." A Bill stays open until a separate BillPayment object links to it, so the hold is just which bills you choose to include the next time you run payments.

// QuickBooks Online has no single "hold" flag on a bill. A Bill stays
// open until a BillPayment links to it (BillPayment.Line[].LinkedTxn) --
// so the hold is simply which bills you choose to include.
async function payOpenBillsIfCompliant(vendorVendMetricId, qboVendorRef, openBills) {
  const check = await vendorIsCompliant(vendorVendMetricId);
  if (!check.ok) {
    notifyProcurement(qboVendorRef, check.reason);
    return; // leave every bill for this vendor unpaid; nothing to release yet
  }

  await fetch(`https://quickbooks.api.intuit.com/v3/company/${REALM_ID}/billpayment`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${QBO_ACCESS_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      VendorRef: { value: qboVendorRef },
      PayType: "Check",
      TotalAmt: openBills.reduce((sum, b) => sum + b.Balance, 0),
      Line: openBills.map((b) => ({
        Amount: b.Balance,
        LinkedTxn: [{ TxnId: b.Id, TxnType: "Bill" }],
      })),
    }),
  });
}

The same shape works for any AP platform with a distinct "pay" step separate from the bill itself: check compliance first, then only include that vendor's bills in the batch you submit.

What you get

  • No payment leaves without a current compliance check.
  • Holds clear themselves the moment the vendor fixes the gap.
  • The whole decision is evidence: the check, the hold, and the release are all traceable to VendMetric's status at that moment.

Start with the API reference, or talk to us about your systems.