mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
Creating a template with an image header failed because the code sent
`example.header_url` — Meta only accepts a Resumable-Upload media handle
(`example.header_handle`) at creation time, never a plain URL.
- Resumable Upload: new `uploadResumableMedia` (meta-api) implements the
two-step `/{app_id}/uploads` flow → media handle. Needs META_APP_ID.
- Handle derivation: `ensureImageHeaderHandle` fetches the header image
(uploaded file OR pasted URL) and turns it into a handle before submit.
Wired into both the submit and edit (PATCH) routes with actionable 400s
(missing META_APP_ID, unreachable URL, wrong type, >5 MB). This fixes
BOTH the new upload path and the previously-broken manual-URL path.
- Template builder: add an image file upload (account-scoped chat-media
bucket via uploadAccountMedia) with preview + JPEG/PNG + 5 MB validation,
alongside the URL field.
- Send-time fix: an approved image template now sends the public
header_media_url as `link`; the resumable header_handle is NO LONGER
used as a send media id (it's invalid for sends and would fail).
- META_APP_ID documented as required for image-header templates.
Scope: image headers (the issue). Helper is generic for video/document.
typecheck/lint/build green; 421 tests (10 new).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88 lines
4.1 KiB
Plaintext
88 lines
4.1 KiB
Plaintext
# ============================================================
|
|
# REQUIRED — the app won't start without these.
|
|
# ============================================================
|
|
|
|
# Supabase (Project Settings → API)
|
|
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
|
|
# Supabase service-role key. Bypasses RLS; used only by server-side
|
|
# routes (the webhook and automation engine). Keep this secret —
|
|
# never paste it into client code.
|
|
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
|
|
|
# WhatsApp token encryption (64 hex chars = 32 bytes, AES-256-GCM).
|
|
# Generate with:
|
|
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
# Rotating this value orphans every token encrypted under the previous
|
|
# key — users have to re-save their WhatsApp settings to reconnect.
|
|
ENCRYPTION_KEY=your-64-char-hex-key-here
|
|
|
|
# Meta App Secret (Meta for Developers → App Settings → Basic).
|
|
# Verifies the HMAC-SHA256 signature on every inbound webhook POST.
|
|
# Required — without it the webhook rejects every request.
|
|
META_APP_SECRET=your-meta-app-secret
|
|
|
|
# ============================================================
|
|
# RECOMMENDED — safe defaults exist but you'll want to set these.
|
|
# ============================================================
|
|
|
|
# Canonical public URL of this deployment (scheme + host, no trailing
|
|
# slash). Used for the sitemap and OG images. Routes that need a
|
|
# self-referential URL (invite links from /api/account/invitations,
|
|
# any future email-bound link) derive the origin from the request
|
|
# itself, so this variable is *only* needed when the request-derived
|
|
# origin would be wrong — e.g. when generating links from a cron job
|
|
# or a background worker that has no incoming request.
|
|
NEXT_PUBLIC_SITE_URL=https://crm.example.com
|
|
|
|
# ============================================================
|
|
# OPTIONAL — only needed if you use the feature.
|
|
# ============================================================
|
|
|
|
# Defense-in-depth allow-list for the hostnames that
|
|
# /api/account/invitations is willing to publish in invite URLs.
|
|
# Comma-separated, no scheme, no port (just hostnames).
|
|
#
|
|
# Why this exists: when NEXT_PUBLIC_SITE_URL is unset, invite URLs
|
|
# are derived from the incoming request's `Host` / `X-Forwarded-
|
|
# Host` header. On a typical proxied deploy the proxy sets these
|
|
# to your canonical hostname and they're trustworthy. On a *bare*
|
|
# deployment exposed to the public internet, an attacker could
|
|
# POST to the API directly with a spoofed `Host: phishing.example`
|
|
# and receive an invite URL pointing at their site.
|
|
#
|
|
# When this var is set, hostnames not on the list are rejected
|
|
# (the request falls through to the wacrm.tech fallback with a
|
|
# console.warn). When unset, behavior is unchanged from earlier
|
|
# versions — the request-derived host is trusted.
|
|
#
|
|
# Most operators don't need this: setting NEXT_PUBLIC_SITE_URL to
|
|
# your canonical URL already pins invite links there. This is for
|
|
# operators who want belt-and-braces or run multi-tenant setups
|
|
# where one app instance serves several hostnames.
|
|
#
|
|
# Example:
|
|
# ALLOWED_INVITE_HOSTS=crm.example.com,crm-staging.example.com
|
|
|
|
# Shared secret protecting GET /api/automations/cron. Required if you
|
|
# use Wait steps in automations (a scheduled pinger drains pending
|
|
# executions). Generate any long random string:
|
|
# openssl rand -hex 32
|
|
# See docs/automations-and-cron.md.
|
|
# AUTOMATION_CRON_SECRET=generate-a-long-random-string
|
|
|
|
# Meta App ID (Meta for Developers → App Settings → Basic). Required to
|
|
# create/edit message templates with an IMAGE header: Meta only accepts a
|
|
# Resumable-Upload media handle (not a plain URL) as the header sample, and
|
|
# that upload is app-scoped. Without it, image-header template submission
|
|
# returns a clear error; text/body-only templates are unaffected. Pair
|
|
# with META_APP_SECRET.
|
|
# META_APP_ID=your-meta-app-id
|
|
|
|
# When "true", POST /api/whatsapp/templates/submit skips the Meta call
|
|
# and stores the row with a synthetic `dry-run-<uuid>` meta_template_id.
|
|
# Set this in CI and local development so you can exercise the full
|
|
# template UI without a real WABA. Leave unset (or "false") in prod.
|
|
# WHATSAPP_TEMPLATES_DRY_RUN=true
|