Quick answer

This tool generates the two functions a PayFast checkout needs: a checkout-signing function that builds and signs the redirect, and an ITN-verification function (an ITN is PayFast's Instant Transaction Notification, the message it sends your site after a payment) that checks PayFast's webhook before you trust it. Our guide to adding a payment gateway to your website explains what each one is for. It never asks for a real merchant ID, key or passphrase. The generated code reads those from environment variables or secrets at runtime instead, and nothing you enter here leaves your browser.

Generate your webhook code

Platform
Environment
Secret variable names
Optional checkout fields

Checkout-signing function + redirect handler

ITN webhook verification function

Nothing you enter here is sent anywhere. This tool runs entirely in your browser.

How to use this

Pick the platform your backend actually runs on and whether you're testing in sandbox or already pointing at live credentials, then edit the three secret variable names only if your project already stores them under different names. Check any optional checkout fields your project needs; they get added to both the payload and the signature string automatically, then copy the two generated functions into your project exactly as they are.

Paste the first function into a new Supabase Edge Function or Base44 backend function to handle checkout creation, and the second into a separate function registered as your PayFast notify_url. Both assume the real secrets already exist in your project's environment variables or secrets manager under the names shown, so set those before testing, not inside the code itself.

Why this tool never asks for a real credential

A tool like this could ask for your actual merchant ID, key and passphrase so the generated code could embed them directly. That's exactly the mistake our guide to adding a payment gateway warns against: a secret written into the code itself, instead of kept in your settings, ends up anywhere that code is copied, shared or shown. This tool generates code that reads those values from environment variables or secrets instead, the same pattern that guide already teaches, and never asks you to type a real credential into a form at all.

The checkboxes and platform toggles you interact with only shape which lines of code get generated; they never touch a network request. Reading your selections and assembling the final function text happens with plain browser JavaScript running on this page, the same code a developer would otherwise type by hand, just faster.

Why the platform choice actually changes the generated code

Lovable and Bolt.new run on a Supabase backend by default, and Claude Artifacts can connect to one, so the generated code reads secrets with Deno.env.get("VAR_NAME"), standard Deno syntax for a Supabase Edge Function. Base44 runs its own backend runtime instead, with its own secrets.get("VAR_NAME") pattern imported from "base44:runtime", a real, confirmed difference between how the two backends expose secrets to server-side code, not a cosmetic naming choice.

That's why this tool's platform toggle reads "Lovable or Bolt.new" rather than listing all three Supabase-connected platforms by name: Claude Artifacts belongs on the same side of the toggle. The generated code is identical across all three; only the surrounding project setup differs.

The Base44 version wraps the handler in Deno.serve(), the entrypoint Base44's Troubleshooting page requires for functions built in its editor. A project built with Base44's CLI exports the handler with export default async function (req: Request) instead, per Base44's developer documentation. In that case, keep the body and swap only the wrapper.

The other difference between the Supabase and Base44 versions is CORS. A Supabase Edge Function that your app calls from the browser has to answer the browser's preflight request and return CORS headers (the browser's permission check before it sends the real request), according to Supabase's documentation as of September 2026. So the Lovable or Bolt.new version of the checkout function imports corsHeaders from @supabase/supabase-js (a path that exists from version 2.95.0) and handles the OPTIONS request. The Base44 version has none of that, because this tool has not verified how Base44's backend functions handle calls from the browser; check that separately. The ITN function never needs it, since PayFast calls it server to server and CORS is a check that browsers enforce. See the CORS error guide for the reasoning.

Why the checkout and the notification are signed differently

PayFast builds both signatures the same basic way: the fields joined into one line of text, in order, with the passphrase handled as below, then hashed with MD5. Neither one sorts the fields first. At checkout, PayFast's documentation says the fields must be listed "in the order in which they appear in the attributes description"; a notification is signed in the order PayFast posted it. Sorting them first (an easy mistake, since alphabetical order looks tidier) produces a signature PayFast never matches. (PayFast's separate API, which this tool doesn't cover, is the one place it does sort.)

Where the two differ is blank fields. When you sign a checkout, PayFast leaves blank fields out, and its code library (its SDK) treats a value of "0" as blank too, so the generated checkout function filters both out and trims each value. When PayFast sends a payment notification, its documentation says the signature string must "include all fields posted from Payfast", blank ones included, in the order they arrived. PayFast's own example notification carries more than a dozen blank fields, such as unused custom fields, so the generated notification function keeps every field exactly as it arrived, up to but not including the signature field itself. Reusing the checkout function for the notification would reject genuine payments.

The passphrase is optional for a one-time payment. PayFast adds it to the signature only if you've set one on your account, and both generated functions do the same: if your passphrase variable is empty or not set, nothing is added.

What the notification function checks before marking an order paid

PayFast's documentation lists four security checks for every notification, and says not to continue if any of them fails. The generated function runs three of them in order:

  1. The signature matches. It's built the notification way described above.
  2. The amount matches your order. amount_gross has to be within one cent of the amount you stored for that order. The generated code has 100.00 as a placeholder; replace it with a lookup of your own order.
  3. PayFast confirms it sent the notification. The function sends the fields back to PayFast's validation address, sandbox or live to match the environment you picked, and continues only if PayFast replies VALID.

The fourth check is that the request came from one of PayFast's own servers. How to confirm that depends on where your function runs, so the generated code leaves a comment pointing to PayFast's documentation rather than guessing. The function replies with a 200 status, the web's standard "received, all fine" code, whether or not the checks pass, as PayFast's own sample does. A failed check is written to your function's logs and the order stays unpaid. That is why the function always replies 200: without a 200 reply, PayFast's documentation (as of September 2026) says it sends the notification again straight away, then after 10 minutes, then at longer intervals.

Why the generated code imports a separate crypto module for MD5

PayFast's signature process specifically requires MD5, and MD5 isn't part of the standard Web Crypto API most modern signed-request code relies on directly. Deno's own @std/crypto module extends crypto.subtle.digest() with MD5 and several other algorithms the native API doesn't include, so the generated code imports { crypto } from jsr:@std/crypto/crypto rather than reaching for a separate third-party MD5 package, keeping the same crypto.subtle.digest() call shape as the standard API.

Sandbox first, always

Leave the environment toggle on sandbox until the full checkout-and-ITN loop works end to end with PayFast's own test credentials. The sandbox runs the exact same code path as production without moving real money. Switching to live changes only the two PayFast addresses in the generated code (the checkout page and the validation address); every other line stays the same. You then replace the sandbox values in your environment variables with your live credentials, which is why there's no reason to test against live credentials first.

Switching the toggle back and forth is intentionally cheap: nothing about the tool remembers or transmits which environment you last selected. Test a full sandbox loop, confirm the ITN signature check passes and the order actually updates, and only then flip to live once you've watched that entire flow succeed for yourself.

What copying this code doesn't do for you

The generated functions handle signing and verification correctly, but the marked comment inside the ITN function, where an order actually gets updated, is deliberately left as a comment, not filled in. Matching a webhook's m_payment_id back to a real order record, handling a failed or cancelled payment status and deciding what a customer sees next are all specific to your own project's data model, not something a generic code generator can respond to responsibly.

The generated code also assumes standard PayFast checkout fields and doesn't account for a merchant account with custom settings, a non-default currency, or a recurring-billing setup layered on top of a one-time payment. If your PayFast dashboard shows options the worked example in PayFast's own integration guide doesn't mention, treat that as a signal to check PayFast's current developer documentation for that specific feature rather than assuming the generated code already covers it.

Independent, with a disclosed Base44 affiliate link

AI Build South Africa is an independent resource. We're not endorsed by, or an official partner of, PayFast, Lovable, Base44, Bolt.new or Anthropic. We do participate in affiliate and referral programmes with some of these providers, including Base44. Generated code is a starting point, not a drop-in production integration. Platforms change their APIs and dashboards without notice, so confirm current field names and endpoints against PayFast's own developer documentation before you rely on this in production. See our Disclaimer for the full disclosure and our Terms of Service for the wider terms.

Frequently asked questions

Does this tool need my real PayFast merchant ID or passphrase?

No, and it never asks. The generated code reads your real credentials from environment variables or secrets at runtime, the same pattern our guide to adding a payment gateway teaches, so a real passphrase never has to be typed into a browser form or shipped inside generated source code.

Why does the generated code differ between Supabase and Base44?

They read secrets differently. A Supabase Edge Function (used by Lovable and Bolt.new by default, and by Claude Artifacts once you connect a Supabase backend) reads Deno.env.get("VAR_NAME"). Base44's own backend runtime uses secrets.get("VAR_NAME") imported from "base44:runtime" instead, a real, confirmed difference between the two platforms' runtimes. The Supabase version also handles CORS preflight, because the browser calls it; the Base44 version does not, because how Base44's backend functions handle browser calls has not been verified.

Why does the checkout function filter out blank fields?

Because PayFast signs a checkout and a notification differently. At checkout it leaves blank fields out, so the checkout function drops them. In a notification it signs every field it sends, blank ones included, so the notification function keeps them all. Reuse one function for both and genuine payments fail the signature check.

What hashing algorithm does the generated code use, and why?

MD5, the algorithm PayFast's own signature process requires. It isn't part of the standard Web Crypto API, so the generated code imports it from Deno's @std/crypto module, which extends crypto.subtle.digest() with MD5 and several other algorithms the native API doesn't include.

Does this tool generate code for subscriptions or refunds too?

No, deliberately not. This generator covers the one-time checkout-and-ITN flow only. Recurring billing, refund API calls and split-payment routing are real PayFast features, just outside what this specific tool generates.

Is the generated code safe to use in production as-is?

It's a genuine, working starting point, not a drop-in production integration. Replace the placeholder amount with a lookup of your own order, add your order-matching and error-handling logic around the "mark the order matching data.m_payment_id as paid here" comment in the notification function, add PayFast's fourth check (that the request came from its servers), test the full sandbox flow end to end and confirm current field names against PayFast's own developer documentation before going live.

Related reading