If your Base44 backend function answers 404 with "Backend function '<name>' not found or not deployed" after a GitHub sync, the code reached your app but the function was never deployed. You fix it by deploying that one function on purpose: base44 functions deploy from the CLI, or one call to Base44's "Redeploy a backend function" endpoint with the function's full source.
What happened on 4 October
I was piping Base44 Partner leads into the CRM in my admin. That took two backend functions: partnerWebhook, which Base44 calls when a new lead arrives, and partnerReply, which sends my replies back. (The whole build is in my signed webhook post.)
They went through my usual loop: pull request, merge into main, let Base44 sync, publish. It's how I ship everything on this site (here's my Claude Code and Base44 setup), and it's exactly what Base44's GitHub docs say to do:
Then I curled the webhook, and Base44 said "not found or not deployed". I polled it for five minutes. Same answer every time.
The files were definitely there. In the app's Base44 sandbox, both entry.ts files matched main byte for byte, same SHA-1. The server just didn't think the functions existed.
If you take one thing from this post: synced isn't deployed. A file in your app and a function that answers requests are two different things. Check the second one yourself.
Is it a Base44 bug or expected behaviour? Honestly, I don't know. These were the first backend functions in this app's repo, and the redeploy docs say "If the app has no backend functions yet, the first redeploy sets them up". That might be why. It's a guess, not a confirmed cause.
- of polling Base44's 404 after sync and publish
- 5 min
- URLs that reach the same function
- 2
- Deno tests passed with a type error in the file
- 40
- of function logs the API returns by default
- 7 days
Test a function from the outside with curl
Don't trust the Publish button. Trust curl. Every deployed function gets an endpoint at https://<your-app-domain>/functions/<function-name>, so you can poke it from a terminal without signing in.
Here are both states on my live site. The first name was never deployed, so you get Base44's full 404, the one partnerWebhook gave me on 4 October. The rest are deployed now:
curl -s -X POST https://aatmanjain.com/functions/notDeployedYet -w '\nHTTP %{http_code}\n'{"error_type":"HTTPException","message":"Backend function 'notDeployedYet' not found or not deployed","detail":"Backend function 'notDeployedYet' not found or not deployed","traceback":"","request_id":null}HTTP 404curl -s -X POST https://aatmanjain.com/functions/partnerWebhook -w '\nHTTP %{http_code}\n'{"error":"Invalid signature."}HTTP 401curl -s -X POST https://aatmanjain.com/api/apps/6ab79c694da396e0e7e13342/functions/partnerWebhook -w '\nHTTP %{http_code}\n'{"error":"Invalid signature."}HTTP 401curl -s -X POST https://aatmanjain.com/functions/partnerReply -w '\nHTTP %{http_code}\n'{"ok":false,"error":"Sign in first."}HTTP 401Those 401s are good news. "Invalid signature." is my webhook's own code refusing an unsigned request, so it's deployed, and it found its signing secret (without one, my code answers 503). "Sign in first." is my reply function noticing there's no user.
Here's the test. Any answer written by your code means the function is live. Only Base44's "not found or not deployed" means it isn't.
Notice both URLs reach the same function: the short /functions/<name> from the docs, and /api/apps/<app_id>/functions/<name>. I give webhooks the short one.
The fix: deploy the function itself
Base44's Apps API has an endpoint called "Redeploy a backend function", and it reads like it was written for my afternoon:
The key line: "The deploy runs even when the code is identical, which is how you recover a function whose deployment is missing or out of date." It deploys the code you send, waits for the deploy to finish, and fails with the compiler's errors if the code doesn't build.
Get a personal access token
In your workspace settings, under Secrets. It must belong to someone with editor access to the app, and it can't be read-only.
Find your app ID
It's in the editor URL:
app.base44.com/apps/<app_id>/editor.Send the whole file
One call per function, with the file from
mainas thecodestring.curl the function
You want your own code's answer, not Base44's 404.
The call, with jq turning the file into a JSON string so you never escape anything by hand:
APP_ID="your_app_id"
jq -Rs '{code: .}' base44/functions/partnerWebhook/entry.ts |
curl -sS -X POST "https://app.base44.com/api/apps/$APP_ID/coding/redeploy-function/partnerWebhook" \
-H "Authorization: Bearer $BASE44_TOKEN" \
-H "Content-Type: application/json" \
--data @-Keep the token in an environment variable, never in the repo. The endpoint also saves what you send as the function's source, so send exactly what's on main. (Still using an old account API key? Base44's docs say those stop working on 15 October 2026.)
It's in beta, and the docs say to "avoid depending on it in production", so I treat it as a repair tool, not my pipeline. An app runs one deploy at a time, and a clash "is safe to retry".
After the redeploy, both answered with the 401s you saw above. Alive.
No API? Use the code editor or the CLI
The same page says the endpoint works "the same way Save & Redeploy does in the builder's code editor", so clicking that should do it too. I went the API route, so I haven't pressed that button on this app myself.
On a CLI project, the Base44 CLI is the normal way in:
base44 functions deploy partnerWebhook partnerReplyOn a GitHub-synced app like mine, be careful. The README Base44's bot wrote in my repo says to publish from the dashboard "rather than base44 deploy", because "a CLI deploy ships your local tree directly, bypassing the sync, and the deployed state silently diverges from the repo." That's why I used the API and sent main's exact file.
How do I deploy this function?
Symptom, cause, fix
| What you see | Likely cause | Fix |
|---|---|---|
404 "not found or not deployed" after publishing | Files synced, function never deployed | Redeploy it |
| The same 404 on a deployed function | Wrong name in the URL | Use the function's name (by default, its folder name) |
403 from the redeploy endpoint | Below Builder, a read-only or workspace key, or no editor access | Upgrade, or use an editor's personal token |
422 with compiler errors | The code doesn't build | Run deno check first |
A secret is undefined | Read at the top of the file | Call secrets.get() inside the handler |
| A webhook's reads or writes fail | Direct HTTP calls have no user | Use asServiceRole |
base44 logs misses live traffic | The CLI reads preview by default | Add --env prod |
Write functions that survive the trip
Read secrets inside the handler
Base44's docs: "Secrets resolve per request, so a read at module load, before any request runs, returns undefined." So this is the trap:
import { secrets } from "base44:runtime";
const SECRET = secrets.get("WEBHOOK_SECRET"); // undefined: no request has run yet
export default async function (req: Request): Promise<Response> {
const secret = secrets.get("WEBHOOK_SECRET"); // this one works
// ...
}Mine read it through a helper the handler calls, with a Deno.env fallback so the same file runs in my local Deno tests. From partnerWebhook:
async function readSecret(name: string): Promise<string | undefined> {
try {
const runtime: any = await import('base44:runtime');
const value = runtime?.secrets?.get?.(name);
if (value) return value;
} catch {
// older runtime, or running locally: fall back to the environment
}
try {
return Deno.env.get(name) || undefined;
} catch {
return undefined;
}
}And it fails closed. With no signing secret, the webhook refuses every delivery with a 503, so nobody can post fake leads while a setting is wrong.
Webhooks have no user, so use asServiceRole
When Base44 Partner calls my webhook, nobody is signed in. The docs: "When calling functions via direct HTTP (like cURL or webhooks), there's no authenticated user context. Use asServiceRole for all operations in these cases."
So partnerWebhook does every read and write through the service role, which skips your access rules completely (my RLS post covers those). Simplified:
const raw = new Uint8Array(await req.arrayBuffer());
const base44 = createClientFromRequest(req);
// no user on a webhook, so the service role does the reads and writes
const db = base44.asServiceRole.entities;
// ...but only after the signature on `raw` checks outWith the service role, your own check is the only lock on the door. So I verify the HMAC on the raw bytes before parsing a single field.
partnerReply is the other case. My admin calls it through the SDK, so there is a user. No user gets "Sign in first.", a non-admin gets "Admins only.", and only then does it touch the database. Know which kind each function is.
Keep API-deployed functions to one file
The redeploy endpoint takes one code string, so a relative import has nothing to point at. Base44's docs warn that "An import that points to a file the function isn't deployed with, such as a file in another function's directory, fails at deploy time."
So both my functions are self-contained, and each file says so up top: "Self-contained on purpose (no shared imports), so it can also be deployed as a single file." Yes, readSecret is copied into both. Fifteen lines of copy and paste is a price I'm happy to pay.
base44/shared/ wins when several functions share real logic and you deploy with the CLI. It uploads the folder, and each function gets only the shared files it imports. The catch, per the docs: "Changing a shared file and deploying a single function updates only that function's copy." Deploy with no names to update them all.
Test locally with Deno before you deploy
Base44 functions run on Deno, so I test mine on Deno 2.9.7 first. The logic lives in exported functions that take the database as an argument (handleDelivery, handleReply), so the tests pass in a fake one and never touch Base44.
All 40 tests passed, from tampered bodies to retries. And there was still a type error in the file, because deno run doesn't type-check and deno check does. Here it is again (I put the bug back to show you):
deno run -A test.ts | tail -140 passed, 0 faileddeno check base44/functions/partnerWebhook/entry.tsCheck base44/functions/partnerWebhook/entry.tsTS2345 [ERROR]: Argument of type 'Uint8Array<ArrayBufferLike>' is not assignable to parameter of type 'BufferSource'. Type 'Uint8Array<ArrayBufferLike>' is not assignable to type 'ArrayBufferView<ArrayBuffer>'. Types of property 'buffer' are incompatible. Type 'ArrayBufferLike' is not assignable to type 'ArrayBuffer'. Type 'SharedArrayBuffer' is missing the following properties from type 'ArrayBuffer': resizable, resize, detached, transfer, transferToFixedLength const expected = `sha256=${hex(await crypto.subtle.sign('HMAC', key, raw))}`; ~~~ at file:///.../base44/functions/partnerWebhook/entry.ts:57:72 error: Type checking failed.The fix was one wrap, which hands crypto.subtle.sign a plain copy backed by an ArrayBuffer:
const expected = `sha256=${hex(await crypto.subtle.sign('HMAC', key, new Uint8Array(raw)))}`;Would Base44's build have rejected the old version? I don't know, and I'd rather find out on my laptop than on a live webhook.
Read the function logs
Once it's live, watch real traffic:
- Dashboard: the docs say you can "check logs for each function from Dashboard → Code → Functions".
- CLI:
base44 logs --function partnerWebhook --env prod. That flag matters, because--envdefaults topreview, the draft. Add-fto stream. - Apps API:
GET /api/apps/{app_id}/functions-mgmt/{function_name}/logsreturns up to 500 lines from the last 7 days. Addenv=prod.
One gotcha from the API docs: "A function that doesn't exist, or that has never run, returns an empty list rather than an error." Empty logs prove nothing.
So here's my rule now. Nothing is live until curl says so. Not the sync, not the Publish button. curl. Stuck on a function that won't deploy? Email hello@aatmanjain.com with what curl says and I'll have a look.
FAQ
Why does my Base44 function say "not found or not deployed"?
That 404 comes from Base44, not your code, and it means there's no deployment for that function name. Check the name in the URL, then deploy the function on purpose with base44 functions deploy, Save & Redeploy in the code editor, or the Apps API's redeploy-function endpoint.
Does GitHub sync deploy Base44 backend functions?
Base44's docs say to merge into main, then click Publish to make changes live. On 4 October that copied my function files into the app but didn't deploy them, and both answered 404 until I redeployed each one. Always curl a new function after you publish.
How do I test a Base44 backend function with curl?
Send a POST to https://<your-app-domain>/functions/<function-name>. Any answer written by your own code, even a 401, means it's deployed, while Base44's "not found or not deployed" 404 means it isn't. Direct HTTP calls have no signed-in user, so use asServiceRole inside the function.
Do I need a paid plan for Base44 backend functions?
Yes. Backend functions need the Builder plan or higher, and so does GitHub two-way sync. The redeploy endpoint returns a 403 when the app's workspace is below Builder.




