Skip to content

My Base44 functions synced from GitHub but never deployed. Here's the fix

On 4 October my GitHub sync copied two Base44 backend functions into my app, I published, and both still answered 404 "not found or not deployed". How to test a function with curl, redeploy it with the CLI or one Apps API call, read secrets and logs properly, and catch bugs first with Deno.

Aatman (AJ) Jain9 min readUpdated 4 Oct 2026
A paper rocket stands ready on its launch pad beside an unopened parcel, with a big orange push button waiting to be pressed, on lime paper.
A paper rocket stands ready on its launch pad beside an unopened parcel, with a big orange push button waiting to be pressed, on lime paper.

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:

Base44's docs (docs.base44.com, 4 October 2026), my highlights. I did both steps.

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.

Synced isn't deployed.

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:

Terminal
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 401

Those 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:

Base44's docs (docs.base44.com, 4 October 2026), my highlights. Note the beta warning.

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.

  1. 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.

  2. Find your app ID

    It's in the editor URL: app.base44.com/apps/<app_id>/editor.

  3. Send the whole file

    One call per function, with the file from main as the code string.

  4. 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:

bash
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:

bash
base44 functions deploy partnerWebhook partnerReply

On 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?

Every path ends with the same curl.

Symptom, cause, fix

What you seeLikely causeFix
404 "not found or not deployed" after publishingFiles synced, function never deployedRedeploy it
The same 404 on a deployed functionWrong name in the URLUse the function's name (by default, its folder name)
403 from the redeploy endpointBelow Builder, a read-only or workspace key, or no editor accessUpgrade, or use an editor's personal token
422 with compiler errorsThe code doesn't buildRun deno check first
A secret is undefinedRead at the top of the fileCall secrets.get() inside the handler
A webhook's reads or writes failDirect HTTP calls have no userUse asServiceRole
base44 logs misses live trafficThe CLI reads preview by defaultAdd --env prod

Write functions that survive the trip

Read secrets inside the handler

Base44's docs (docs.base44.com, 4 October 2026), my highlights.

Base44's docs: "Secrets resolve per request, so a read at module load, before any request runs, returns undefined." So this is the trap:

ts
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:

ts
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

Base44's docs (docs.base44.com, 4 October 2026), my notes.

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:

ts
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 out

With 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):

Terminal
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:

ts
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 --env defaults to preview, the draft. Add -f to stream.
  • Apps API: GET /api/apps/{app_id}/functions-mgmt/{function_name}/logs returns up to 500 lines from the last 7 days. Add env=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.

Keep reading

More posts on the same things.

Newsletter

Liked this one?

Get my Friday email: new posts, what I built and the Base44 and AI news worth knowing.

No spam. Unsubscribe whenever.

Got an app in mind?

I build and fix Base44 apps, and I'm looking for a remote internship. Tell me what you're working on.