Skip to content

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. in ir.config_parameter or 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

FlowDirectionMechanism
Leads into OdooOffertown → OdooA scheduled action (ir.cron) polls the referral list endpoint and creates crm.lead records.
Conversions backOdoo → OffertownAn automated action fires on your conversion event and calls the convert endpoint.
Outstanding vouchers redeemedOdoo → OffertownFor 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

GET/api/v1/partners/referrals

Run a scheduled action every 5–15 minutes that keeps a watermark of the last successful sync:

  1. Call the list endpoint with status=pending, dateFrom set to the watermark, dateTo set to the sync start time, and sortBy=createdAt&sortOrder=asc, paginating with page/pageSize. If the response reports pagination.truncated: true, split the window and repeat.
  2. For each referral whose referralId isn’t in Odoo yet, create a crm.lead using the mapping below. Put a unique constraint on x_offertown_referral_id so replays are harmless no-ops.
  3. Advance the watermark to the run’s dateTo only 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 fieldOdoo crm.lead field
referralIdx_offertown_referral_id (custom, indexed) — the join key
referralCodex_offertown_referral_code (custom) — handy for support
—name (required by crm.lead) — e.g. “Offertown referral — ” + friendName
friendName / friendEmail / friendPhonecontact_name / email_from / phone
friendPostCode / friendAddress / friendCityzip / street / city — useful for serviceability checks
referrerName / referrerEmailx_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

POST/api/v1/partners/referrals/{referralId}/convert

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:

Request body
{
  "conversionValue": 12999,                  // order value in cents (EUR)
  "currency": "EUR",
  "externalTransactionId": "INV/2026/00042"  // your invoice reference
}
  • externalTransactionId is 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 the account.move database ID.
  • Reusing a transaction ID against a different referral is rejected with 409 DUPLICATE_CONVERSION.
  • On 429, honor Retry-After and 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

GET/api/v1/partners/vouchers/{code}
POST/api/v1/partners/vouchers/{code}/redeem

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).

  1. Add a custom field x_offertown_voucher_code to account.move, where staff enter the code.
  2. When the code is entered, call the lookup endpoint. If valid is true, show customerName so staff can check it matches, and add a discount line for amountCents. A voucher is always redeemed for its full amount. If valid is false, show the reason and add no discount.
  3. 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, override action_post in your module). If the call fails, raise an error so the posting rolls back and no discount is given without a redeemed voucher.
Request body
{
  "partnerReference": "move-58213"   // stable, globally unique invoice reference
}
  • Use a reference that stays the same across retries. The account.move database 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 returns 409 ALREADY_REDEEMED.
  • 404 INVALID means the code is unknown or belongs to another business. 409 EXPIRED and 409 UNAVAILABLE mean 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_LOCKED for up to 15 minutes. Have staff copy codes exactly, and honor Retry-After on any 429.

Every redemption shows up in the partner dashboard under Vouchers, with your reference next to it.

Capturing leads on your own Odoo website

POST/api/v1/partners/referrals

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:

Request body
{
  "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

  1. Create a test referral through your capture path (widget, hosted form, or the create endpoint).
  2. Wait for (or manually trigger) the sync cron — the lead appears in Odoo. (Self-captured leads from the create endpoint are already there.)
  3. Mark it converted in Odoo (pay a test invoice) — the automated action fires.
  4. Confirm the referral shows converted and 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.