Connect Bubble to AppClickPrint
Use Bubble Backend Workflows and the API Connector’s private authentication fields. Never place either AppClickPrint key in a page element, Option Set or client workflow.
Read https://appclickprint.com/integrations/bubble.html and https://appclickprint.com/developers.html completely before editing.
Use Bubble Backend Workflows plus server-routed API Connector actions. Preserve this project's existing authentication and tenant model. Implement provisioning, setup-status polling, printer retrieval and selection, print submission, stable job-ID storage, and terminal webhooks if the UI shows final outcomes. Add two permanent customer controls: Add Gateway calls the authenticated host backend, which requests POST /v1/developer/customers/{externalId}/gateway-code and returns only the temporary setupCode, qrCodeDataUrl, expiresAt, remainingUses and playStoreUrl for display; Manage Gateways requests a fresh mode: "gateways" portal session and opens its one-use URL on every click. Show Google Play, QR-scanning and manual-code instructions beside Add Gateway. Record gateway_count before showing the code and detect success when it increases, including when setup_status was already complete. Refresh printers after registration or management and require reselection if the selected printer belonged to a removed gateway. Never expose acm_, acp_ or webhook secrets to frontend code or responses, and never put them in the QR code. Treat HTTP 202 as queued, not printed. Do not skip tests.Use the platform’s real server boundary
| Responsibility | Bubble implementation |
|---|---|
| AppClickPrint API calls | Bubble Backend Workflows plus server-routed API Connector actions |
| Management key | an API Connector private Authorization header for the single management key; never a non-private parameter |
| Customer print keys | a Customer/Organisation field protected by strict privacy rules for the per-customer print key; use an external serverless function if your design cannot guarantee the dynamic key remains server-side |
| Frontend boundary | Page workflows trigger authenticated backend workflows. Resolve the Current User’s organisation inside Bubble and do not accept another organisation’s external ID or printer alias without ownership checks. |
| Deployment check | Initialise API Connector calls with test values, then verify private fields are absent from downloaded app code and browser requests. Test development and live origins separately. |
acm_ is the one-per-developer management key used for customers, onboarding, status, printers and webhooks. Each customer’s acp_ key is used only to submit that customer’s print jobs. Neither key may appear in the customer-facing frontend.Required end-to-end flow—do not stop early
- Create one AppClickPrint developer account and store the returned
acm_key in Bubble’s server secret store. - When a tenant enables printing, provision one AppClickPrint customer using the host app’s immutable tenant ID as
externalId. - Encrypt and store the returned one-time
acp_key against that tenant. Return no key to the frontend. - Add a permanent Add Gateway button. The frontend calls an authenticated Bubble backend function; that function requests
POST …/gateway-code. - Show the returned QR image, manual setup code, expiry and Google Play link with clear Android gateway instructions.
- Record the customer’s current
gateway_count, then poll customer status every 2–5 seconds while the Add Gateway panel is open. - When
gateway_countincreases, retrieve printers and let the user choose anavailableprinter. - Store both immutable printer
idand current routingaliason the existing tenant/location/settings record. - Add a permanent Manage Gateways button. On every click, request a fresh portal session with
mode: "gateways"so the customer can rename, rediscover, replace or remove gateways and configure printers. - After gateway management, retrieve printers again and require reselection if the selected printer is no longer available or belonged to a removed gateway.
- Submit jobs from the backend with the tenant’s
acp_key, current alias and stable idempotency key. - Persist the returned job
id. HTTP 202 means queued. - If the product promises final status, receive and verify
job.completed/job.failedwebhooks.
Provision once; resume without reprovisioning
POST https://appclickprint.com/v1/developer/customers
Authorization: Bearer acm_YOUR_MANAGEMENT_KEY
Idempotency-Key: provision:{stableTenantId}:v1
Content-Type: application/json
{"externalId":"{stableTenantId}","businessName":"Example Company"}On HTTP 201, save apiKey before returning a safe response such as { created, customerId }. The browser must not receive the raw AppClickPrint response because it contains apiKey. Repeating an existing externalId returns created: false without the existing key; losing it requires deliberate key rotation.
Implement Add Gateway in Bubble
Keep Add Gateway available before and after initial setup. The authenticated frontend invokes a tenant-scoped Bubble backend function; only that function uses the acm_ key:
POST https://appclickprint.com/v1/developer/customers/{externalId}/gateway-code
Authorization: Bearer acm_YOUR_MANAGEMENT_KEY
Content-Type: application/jsonHTTP/1.1 201 Created
{
"setupCode": "ABCD-2345",
"qrCodeDataUrl": "data:image/png;base64,iVBORw0KGgo…",
"expiresAt": "2026-08-15T04:00:00.000Z",
"remainingUses": 3,
"playStoreUrl": "https://play.google.com/store/apps/details?id=com.appclickprint"
}The backend may return only those temporary display fields to the authenticated intended customer. Render qrCodeDataUrl as the preferred setup option and show setupCode as a manual fallback. The QR payload is exactly the temporary setup code—never an acm_ key, acp_ key, external customer ID or host-app login token. Request a new code on every Add Gateway click; it expires after 30 minutes and can register up to three gateways.
Required instructions beside the code
- Install AppClickPrint Gateway from the returned Google Play link on an Android device that will remain near the printers.
- Open the app and enter a meaningful gateway name such as “Reception”.
- Tap Scan QR code and scan the displayed QR, or choose the manual option and enter the setup code.
- Allow discovery and follow the app’s battery/background-running recommendations.
GET /v1/developer/customers/{externalId}
Authorization: Bearer acm_YOUR_MANAGEMENT_KEY
setup_status:
- waiting_for_gateway
- discovering_printers
- completecomplete means a gateway is registered and at least one printer was discovered. It does not mean a test print succeeded. Before generating a code, record gateway_count. Stop polling when it increases, the user cancels or the UI times out. This count comparison is required for additional gateways because setup_status may already be complete.
Keep Manage Gateways available after setup
One AppClickPrint customer can register multiple Android gateways—for printers in different areas, device replacement or resilience. Do not hide management after the first gateway or printer is configured. On every Manage Gateways click, the authenticated frontend asks the Bubble backend to create this session:
POST https://appclickprint.com/v1/developer/customers/{externalId}/portal-session
Authorization: Bearer acm_YOUR_MANAGEMENT_KEY
Content-Type: application/json
{"mode":"gateways"}Return only the temporary url and expiresAt to the frontend, then open the URL in a new tab with noopener. The tenant administration page lets the customer add, rename, rediscover, replace or remove gateways, configure printers and send test pages. The URL is short-lived and one-use: create a new session on every click, never provision another customer, and never rotate the acp_ key merely to manage a gateway.
Retrieve, select and refresh printers
GET /v1/developer/customers/{externalId}/printers
Authorization: Bearer acm_YOUR_MANAGEMENT_KEYShow only printers returned for the authenticated tenant and normally require available: true. Store id for identity and alias for routing. Aliases can be edited, and adding or replacing a gateway can create new printer IDs and aliases. Refresh the list after setup, whenever the customer returns from gateway management, and when a saved alias is rejected. If the selected printer belonged to a removed gateway, require the user to select an available printer again. available reflects the latest discovery report; it is not proof of real-time reachability.
For an AppClickPrint-hosted picker, register the exact Bubble production/preview origin, create mode: "select" with that origin, and accept appclickprint.printer.selected only when event.origin === "https://appclickprint.com".
Required customer-facing printing controls
Do not implement only the backend. Add these controls to the existing tenant, location or feature-settings screen in Bubble:
| Control | Required behavior |
|---|---|
| Add Gateway | Keep this visible before and after setup. Ask the authenticated backend for a fresh gateway-code, then show its QR image, manual code, expiry, Google Play link and instructions. Record gateway_count first and refresh printers when it increases. |
| Manage Gateways | Keep this visible before and after setup. Ask the backend for a fresh mode: "gateways" session on every click and open the temporary tenant-admin URL. Refresh printers when the user returns. |
| Select printer | Ask the backend for mode: "select" with the exact allow-listed app origin, then open the returned one-use URL in a popup or new tab. |
| Selected printer | Display the saved name as a snapshot plus a status such as Available or Unavailable from the latest backend refresh. Provide Change printer, and require reselection when its gateway was removed. |
| Disable until a valid printer ID and current alias are selected. If submission says the printer is unavailable/not found, keep the preference visible and offer Reselect printer. |
Add Gateway button flow
// The Bubble frontend calls its authenticated backend.
// The backend derives externalId from the signed-in tenant and uses acm_.
async function addGateway() {
const before = await hostBackend.getAppClickPrintCustomerStatus();
const setup = await hostBackend.createGatewayCode();
showGatewaySetup({
qrImageSrc: setup.qrCodeDataUrl,
manualCode: setup.setupCode,
expiresAt: setup.expiresAt,
playStoreUrl: setup.playStoreUrl
});
// Poll through the backend every 2–5 seconds while this panel is open.
// Success is a count increase, even if setup_status was already complete.
await waitUntilGatewayCountExceeds(Number(before.gateway_count));
await refreshPrinters();
}Manage Gateways button flow
// The frontend calls your authenticated Bubble backend; only the backend uses acm_.
async function manageGateways() {
const { url } = await hostBackend.createPortalSession({ mode: "gateways" });
window.open(url, "_blank", "noopener");
// When the user returns, fetch printers again before displaying or printing.
}Select printer button flow
// Browser/mobile web UI calls your authenticated Bubble backend.
async function selectPrinter() {
const { url } = await hostBackend.createPortalSession({ mode: "select" });
window.open(url, "appclickprint-select", "width=1100,height=760");
}
window.addEventListener("message", async (event) => {
if (event.origin !== "https://appclickprint.com") return;
if (event.data?.type !== "appclickprint.printer.selected") return;
// Send only the selection to your authenticated backend.
// The backend re-fetches this tenant's printer list before saving it.
await hostBackend.savePrinterSelection({
printerId: event.data.printer.id,
printerAlias: event.data.printer.alias,
printerName: event.data.printer.name
});
});The selection popup must retain its opener so AppClickPrint can return the selection with postMessage; do not add noopener to this specific popup. If the platform forces noopener, close the picker manually and refresh printers from the backend instead.
Page workflows trigger authenticated backend workflows. Resolve the Current User’s organisation inside Bubble and do not accept another organisation’s external ID or printer alias without ownership checks.
postMessage or arbitrary alias from the browser. The backend must call the customer printer-list endpoint and confirm that the submitted ID and alias belong to the authenticated tenant before saving them.Print dimensions belong to the document/template
For PDF jobs, store physical dimensions with the host app’s label, receipt or document template—for example 62 × 29 mm for a badge. Generate the PDF page at that exact size and have the backend send the same values as widthMm=62 and heightMm=29. Do not guess dimensions from pixels, the browser viewport, the printer name or a frontend preview.
Saved print template - widthMm: 62 - heightMm: 29 - orientation: landscape Generated PDF page: 62 mm × 29 mm Submitted job: widthMm=62, heightMm=29
A PDF whose page is 29 × 62 mm is also accepted as the rotated orientation of 62 × 29 mm. ZPL and ESC/POS jobs use printer-native commands and omit widthMm/heightMm. Copies must be between 1 and 100.
Submit from the backend
const form = new FormData();
form.set("format", "pdf");
form.set("documentUrl", signedPdfUrl);
form.set("filename", "label.pdf");
form.set("widthMm", "62");
form.set("heightMm", "29");
form.set("copies", "1");
form.set("photoMode", "true"); // Optional per-job override; omit to use the saved printer setting.
form.set("idempotencyKey", `host-job:${businessJobId}:v1`);
const response = await fetch(
`https://appclickprint.com/v1/printers/${encodeURIComponent(alias)}/print`,
{ method: "POST", headers: { Authorization: `Bearer ${customerPrintKey}` }, body: form }
);
const job = await response.json();
if (!response.ok) throw new Error(job.message || job.error);
await saveAppClickPrintJobId(businessJobId, job.id);
return { jobId: job.id, status: job.status }; // queued, not printedFor PDF, the page size must match the supplied millimetres. Optional Boolean photoMode overrides the saved printer setting for that job: true improves photographs using compatible raster drivers, false favours crisp text and barcodes, and omission inherits the saved setting. Native IPP/AirPrint and operating-system drivers process images themselves. Raw ZPL and ESC/POS jobs do not accept photoMode. ZPL and ESC/POS are also supported when the selected driver matches. If a gateway is temporarily offline but the printer remains available, an accepted job queues for reconnect; ensure a signed documentUrl remains valid long enough.
Use accurate outcomes and safe retries
202 { id, status: "queued" }means accepted and stored—not printed.- Current terminal webhooks are
job.completedandjob.failed. VerifyX-AppClickPrint-Signatureagainst the raw body with the one-timewhsec_secret. completedmeans the Android gateway reports its print operation completed; some protocols cannot prove paper physically emerged.- There is no public job-status GET endpoint or intermediate claimed/printing webhook yet.
- Retry network timeouts, 429 and 5xx with the same idempotency key and exponential backoff. Do not retry unchanged 4xx requests.
- Use the
Idempotency-Keyheader for provisioning, but theidempotencyKeybody/form field for printing.
Bubble release checklist
- Search source, generated bundles, browser responses and logs for
acm_,acp_andwhsec_; none may be exposed. - Prove one signed-in tenant cannot provision, create gateway codes, inspect printers or print as another tenant.
- Retry provisioning and confirm only one AppClickPrint customer exists.
- Click Add Gateway twice and confirm each attempt returns fresh temporary display details for the same customer without rotating its
acp_key. - Confirm the QR image and manual code are both usable, expiry and Google Play instructions are visible, and no permanent credential is encoded in the QR.
- Register the first gateway and confirm
gateway_countincreases, printers refresh and the user can select an available printer. - Register a second gateway while
setup_statusis already complete; confirm the count-based detector succeeds without creating another customer. - Open Manage Gateways twice and confirm each click creates a fresh one-use
mode: "gateways"URL. - Remove a gateway through Manage Gateways, refresh printers and confirm a printer on that gateway must be reselected.
- Submit the same print idempotency key twice and confirm the same AppClickPrint job ID is returned.
- Take the gateway offline, confirm the UI says queued rather than printed, then reconnect it.
- If final status is shown, validate webhook HMAC using the raw body and deduplicate by event ID.
- Initialise API Connector calls with test values, then verify private fields are absent from downloaded app code and browser requests. Test development and live origins separately.
Platform approach checked against official Bubble API Connector security.