fixxo
Contact consent Website Onboarding
Consent capture

How we ask before we contact.

This is the flow that makes a collected phone number usable — for SMS, for WhatsApp, and for AI-assisted calls. Without it, those channels are illegal in the US. With it, they're the fastest way to reach a tradesperson.

01

The component

Affirmative, unbundled, and unchecked by default — a pre-ticked box is not consent under the TCPA. The exact wording shown is stored verbatim with a timestamp, the page URL and the IP.

State 1 · Default
Get partner details by text

No lead fees, 82–85% of the completed job, paid 3–7 days after the photo check.

I agree to receive calls and text messages, including messages generated by AI-assisted systems, from Fixxo at the number provided. Consent is not a condition of any purchase. Message frequency varies. Reply STOP to opt out, HELP for help. Msg & data rates may apply.

The button stays disabled until the box is ticked. That's the point.

State 2 · Confirmation sent
Preview

Ticking the box enables the send, and one confirmation message goes out.

State 3 · Confirmed
Consent recorded

You can ask us to stop at any time, in any way — reply STOP, say it on a call, or email. We honour it across every channel, and we never re-add you.

Continue to onboarding

No account, no spam, no third parties.

02

What we store

One row per number, per channel. This is the record we'd hand to a regulator, so it holds the exact words shown and everything needed to prove when and how.

-- consent_records (db/outreach_schema.sql) { "phone": "+18045550142", "channel": "sms", "kind": "pewc", // prior express written consent "consent_language": "I agree to receive calls and text messages, including messages generated by AI-assisted systems, from Fixxo at the number provided. Consent is not a condition of any purchase. ... Reply STOP to opt out.", "named_business": "Fixxo", "source": "partner_landing_form", "source_url": "https://fixxo.com/technicians", "captured_at": "2026-11-04T14:22:08Z", "captured_ip": "73.**.**.14", "is_condition_of_purchase": false, "revoked_at": null }

WhatsApp is recorded as a separate channel row, because Meta requires its own opt-in. `can_contact(phone, 'sms')` and `can_contact(phone, 'whatsapp')` are checked separately at send time.

03

The rules this flow follows

DoLeave the box unchecked. Affirmative consent means the person acted; a pre-ticked box is not consent.
DoName Fixxo in the consent language. "Our partners" is not consent.
DoState that consent is not a condition of purchase.
DoDisclose AI-assisted messages where AI will be involved.
DoDouble opt-in: one confirmation message, then nothing until they reply YES.
DoStore the exact wording, timestamp, URL and IP. If it isn't written down, it didn't happen.
DoKeep one shared suppression list that every channel reads before sending.
Don'tPre-tick the box, bundle consent into Terms, or bury it in a link.
Don'tText or WhatsApp a number that hasn't opted in — including numbers from a purchased list.
Don'tMessage outside 8:00–21:00 local time, in any channel.
Don'tRequire a specific keyword to opt out. Any reasonable means works, honoured within 10 business days.
Don'tRecord a call without spoken consent.

Full legal basis and penalties: outreach/COMPLIANCE.md. The enforcement function: db/outreach_schema.sqlcan_contact().

04

Live on the site

The same module is embedded in the technician section of the homepage and on the technician landing page, so every number collected on the site arrives with its consent record attached.

See it on the homepage Technician page