Integration Guides

Technical walkthroughs for wiring up external services and platform features.

Facebook Auto-Posting

Approved listings and published blog posts are automatically cross-posted to the Barnyard Listing Facebook page. The flow is policy-safe by design.

Prerequisites

The facebook_pages OAuth connector must be authorized (shared connection) with scopes pages_show_list, pages_manage_posts, pages_read_engagement. Authorization is managed in the app's integration settings.

Flow

1. Admin approves a listing (admin-portal: approve_listing)
2. admin-portal calls postListingToFacebook(base44, listingId)
   └─ shared/fb-post.ts
      a. Fetch the page access token via the connector
      b. Run AI safety check on the cover photo (InvokeLLM)
         └─ if unsafe → swap in a branded golden-hour graphic
      c. Build a policy-safe message (category + link, NO price)
      d. POST to the Graph API /feed or /photos
      e. Update listing: fb_posted=true, fb_post_id, fb_posted_date
      f. Create a FacebookPost audit record

Policy compliance

Facebook posts never include price, transactional terms, or contact info — only the category, a short label, and the public listing link. This keeps posts compliant with Meta's commerce policies. All categories are eligible provided their photos pass the safety check.

Manual re-posting

Admins can manually trigger a re-post from the admin portal, which invokes the post-to-facebook (listings) or post-blog-to-facebook (blog) function.

Base44 Payments (Wix) — Ad Subscriptions

Paid ad banners run on recurring subscriptions. The payment pipe has three parts: checkout creation, webhook confirmation, and subscription lifecycle handling.

1. Checkout creation

An admin initiates a subscription from the Advertise page. The create-checkout function (admin-gated) builds a Wix checkout session and stores a Pending Advertisement linked by checkout_id.

Plans

Monthly    $49/mo   (frequency MONTH, interval 1)
Quarterly  $129/mo  (frequency MONTH, interval 3)
Annual     $399/yr  (frequency YEAR, interval 1)

The buyer is redirected to checkoutSession.redirectUrl (from response.data.redirectUrl).

2. Webhook confirmation

Wix calls wix-payments-webhook on order_approved. The function verifies the JWT, resolves the ad by order.checkoutId, stores the subscription ID, and advances the ad to Pending Review. An admin then approves the ad to Active.

Correlation key

Wix provides no custom-metadata field, so checkout_id is the only reliable link from a payment back to the buyer's ad. Never correlate on the email typed at checkout.

3. Subscription lifecycle

The webhook is also registered for SUBSCRIPTION_CANCELED and SUBSCRIPTION_ENDED. On either event, the matching ad (found by subscription_id) is marked Expired and stops displaying.

Secrets

The function reads WIX_PAYMENTS_API_KEY, WIX_PAYMENTS_SITE_ID, and WIX_PAYMENTS_WEBHOOK_PUBLIC_KEY from app secrets. The return URL is built from WIX_CHECKOUT_APP_URL (falling back to the app domain), never from caller headers.

Backend Functions — Invocation Pattern

From the frontend, always invoke backend functions through the SDK — never a raw fetch (the bare path returns 405).

Frontend invocation

import { base44 } from "@/api/base44Client";

// Create a listing
const res = await base44.functions.invoke("create-listing", data);
const listingId = res.data.id;

// Update a listing
const res = await base44.functions.invoke("update-listing", { id, ...data });
const updated = res.data.listing;

// Notify admins (fire-and-forget)
base44.functions.invoke("notify-admin", { action: "new_listing", listing_id })
  .catch(() => {});

Each function returns an Axios-style response; read the payload from res.data.

Row-Level Security Patterns

RLS is configured per entity in the rls block of its schema. The common patterns used in this app:

Owner-scoped (Inquiry)

"read": {
  "$or": [
    { "data.listing_owner_id": "{{user.id}}" },
    { "user_condition": { "role": "admin" } }
  ]
}

Only the listing owner or an admin can read. The {{user.id}} token is a template variable resolved server-side.

Admin-only writes (Listing, Advertisement, BlogPost, FacebookPost)

"create": { "user_condition": { "role": "admin" } },
"update": { "user_condition": { "role": "admin" } },
"delete": { "user_condition": { "role": "admin" } }

Direct SDK writes are blocked for normal users. They instead go through service-role backend functions that enforce field-level control.

Owner-or-admin delete (Listing)

"delete": {
  "$or": [
    { "created_by_id": "{{user.id}}" },
    { "user_condition": { "role": "admin" } }
  ]
}

Lock-out caution

A too-tight read rule can lock users out of their own data. Always include the owner condition alongside admin conditions, and test with a non-admin account after changing RLS.

Listing Create / Update Flow

The secure boundary around listing writes is the most important integration pattern to understand.

         ┌──────────────────────────────────────────┐
Browser  │  create-listing / update-listing (service) │
  ──────►│  • auth.me()                              │
         │  • whitelist user fields                  │
         │  • anchor contact_email/phone to account  │
         │  • strip system fields                    │
         │  • asServiceRole.entities.Listing.create  │
         └──────────────────────────────────────────┘
                       │  (RLS blocks direct SDK writes for non-admins)
                       ▼
                   Database

Because Listing create/update RLS is admin-only, the backend function (running as service role) is the only path a normal user has to create or edit their listings. This guarantees no system field can be tampered with from the browser.