Odoo CRM guide
Connect an Odoo CRM to your Offertown program: referral leads flow into Odoo as CRM leads, conversions flow back the moment a referred lead becomes a paying customer, and any reward voucher issued on or before 16 October 2026 can still be redeemed on your invoices. Built entirely on the REST API — a small connector you control, no Offertown code inside your Odoo database.
Requirements
- An approved Offertown partner account with an API key (
oft_…) from the partner dashboard — keep it server-side, e.g. inir.config_parameteror your connector’s settings. - Odoo 16 or newer. On odoo.sh or on-premise the connector is a small custom module; on Odoo Online (SaaS), where custom Python isn’t available, run the same logic as an external service talking to Odoo over its external API — note Odoo Online only exposes the external API on Custom plans.
How the integration fits together
| Flow | Direction | Mechanism |
|---|---|---|
| Leads into Odoo | Offertown → Odoo | A scheduled action (ir.cron) polls the referral list endpoint and creates crm.lead records. |
| Conversions back | Odoo → Offertown | An automated action fires on your conversion event and calls the convert endpoint. |
| Outstanding vouchers redeemed | Odoo → Offertown | For a voucher issued on or before 16 October 2026, your invoice checks the customer’s code, applies it as a discount, and redeems it when the invoice is posted. |
The join key is the Offertown referralId, stored in a custom field on the lead at import time. It travels with the record through your pipeline, so reporting the conversion later needs no fuzzy matching on emails or names.
Flow 1 — sync referrals into Odoo
Run a scheduled action every 5–15 minutes that keeps a watermark of the last successful sync:
- Call the list endpoint with
status=pending,dateFromset to the watermark,dateToset to the sync start time, andsortBy=createdAt&sortOrder=asc, paginating withpage/pageSize. If the response reportspagination.truncated: true, split the window and repeat. - For each referral whose
referralIdisn’t in Odoo yet, create acrm.leadusing the mapping below. Put a unique constraint onx_offertown_referral_idso replays are harmless no-ops. - Advance the watermark to the run’s
dateToonly after the run completes successfully.
One subtlety: a referral can start as clicked and become pending later without its createdAt changing, so a creation-time watermark alone can miss late-identified leads. Add a periodic reconciliation pass (daily is fine) that re-queries status=pending over the full 90-day attribution window — the referralId dedupe makes it idempotent.
| Offertown field | Odoo crm.lead field |
|---|---|
referralId | x_offertown_referral_id (custom, indexed) — the join key |
referralCode | x_offertown_referral_code (custom) — handy for support |
| — | name (required by crm.lead) — e.g. “Offertown referral — ” + friendName |
friendName / friendEmail / friendPhone | contact_name / email_from / phone |
friendPostCode / friendAddress / friendCity | zip / street / city — useful for serviceability checks |
referrerName / referrerEmail | x_offertown_referrer (custom) — who referred this lead |
| — | source_id set to a fixed “Offertown Referral” utm.source, so referral pipeline performance is reportable inside Odoo |
Two-way traceability: PATCH the referral’s externalSourceId with the Odoo lead ID after import (see the API reference). The optional capture fields (phone, address, sourceUrl) are present only when the friend provided them on the form. consentAt records the consent checkbox on Offertown-rendered forms; for programmatic captures that omit it, it is the submission timestamp rather than proof of consent.
Flow 2 — report conversions from Odoo
Attach an automated action to whichever Odoo event you treat as the real conversion — for most subscription businesses that’s first invoice paid (account.move reaching payment_state = paid) rather than merely opportunity won. Note the referral field lives on crm.lead: an automation on account.move must traverse back to it (invoice lines → sale order → opportunity_id), or — simpler — propagate x_offertown_referral_id onto sale.order and account.move when they’re created. Either way the action then calls:
{
"conversionValue": 12999, // order value in cents (EUR)
"currency": "EUR",
"externalTransactionId": "INV/2026/00042" // your invoice reference
}externalTransactionIdis the idempotency key. Retries with the same ID safely return the original result, so the action can retry on network errors without risking double rewards. Odoo invoice names are only unique per journal, so use a globally unique reference — e.g. prefix with the company or journal, or use theaccount.movedatabase ID.- Reusing a transaction ID against a different referral is rejected with
409 DUPLICATE_CONVERSION. - On
429, honorRetry-Afterand retry — writes are limited to 30 per minute per IP, and conversion reporting to 60 per minute per partner.
What happens next — reward approval, maturation, and billing — is described under money flow in the overview. Conversions you report land as pending rewards that you approve in the partner dashboard before anything is charged.
Flow 3 — redeem outstanding vouchers from Odoo
Rewards are taken as brand codes from Offertown and are not spent with you, so no new vouchers are issued. A voucher issued on or before 16 October 2026 stays valid with the business that funded it until it is used or expires, and this flow redeems it. If none of your customers holds one, you can skip it.
A voucher code looks like VCH-AB12CD. When a customer hands one over, your invoice takes the voucher amount off the price and tells Offertown the voucher has been used. Offertown then pays you the voucher amount (see money flow in the overview).
- Add a custom field
x_offertown_voucher_codetoaccount.move, where staff enter the code. - When the code is entered, call the lookup endpoint. If
validistrue, showcustomerNameso staff can check it matches, and add a discount line foramountCents. A voucher is always redeemed for its full amount. Ifvalidisfalse, show thereasonand add no discount. - When the invoice is posted, call the redeem endpoint with your invoice reference as
partnerReference. Do this in the same transaction as the posting (for example, overrideaction_postin your module). If the call fails, raise an error so the posting rolls back and no discount is given without a redeemed voucher.
{
"partnerReference": "move-58213" // stable, globally unique invoice reference
}- Use a reference that stays the same across retries. The
account.movedatabase ID is the safest choice. The invoice name is only assigned at posting, and it is only unique per journal. - On a network error or timeout, retry with the same reference. If the first call went through, the retry returns the original result with
idempotent: true, and the voucher is never redeemed twice. A different reference on an already-used voucher returns409 ALREADY_REDEEMED. 404 INVALIDmeans the code is unknown or belongs to another business.409 EXPIREDand409 UNAVAILABLEmean the voucher can’t be used right now. In each case, remove the discount and tell the customer.- Unknown codes count toward a limit shared with your partner dashboard. After more than 12 unknown codes in 5 minutes, redemptions return
429 VOUCHER_ENUMERATION_LOCKEDfor up to 15 minutes. Have staff copy codes exactly, and honorRetry-Afteron any429.
Every redemption shows up in the partner dashboard under Vouchers, with your reference next to it.
Capturing leads on your own Odoo website
If you host the referral signup form yourself (for example an Odoo website form), configure your program’s partner-hosted signup URL in the dashboard: referral links then redirect to your page with ?ref_code=… attached. Capture the code with the form and register the lead server-side:
{
"referrerIdentifier": "a1b2c3d4", // the ref_code from the URL
"friendEmail": "ana@example.com",
"friendName": "Ana",
"externalSourceId": "odoo-lead-4211" // your crm.lead ID, for traceability
}Copy the referralId from the response into the lead’s x_offertown_referral_id — Flow 2 needs it to report the conversion later. This replaces Flow 1 for leads you capture yourself: the referral is created attributed and already in your CRM. Partners using the embeddable widget or Offertown-hosted forms instead pick leads up via Flow 1 polling.
Testing the loop end-to-end
- Create a test referral through your capture path (widget, hosted form, or the create endpoint).
- Wait for (or manually trigger) the sync cron — the lead appears in Odoo. (Self-captured leads from the create endpoint are already there.)
- Mark it converted in Odoo (pay a test invoice) — the automated action fires.
- Confirm the referral shows
convertedand a pending reward appears in your partner dashboard.
Full API details
Authentication, rate limits, error shapes, and every endpoint used above are documented in the REST API reference.