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 recordPolicy 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)
▼
DatabaseBecause 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.