> For the complete documentation index, see [llms.txt](https://help.tahua.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.tahua.io/product-news/2026/readme/fundraiserone-contact-sync-integration.md).

# FundraiserOne Contact Sync Integration

## Overview

A per-organisation integration that syncs registration entities from Tahua to FundraiserOne (F1) as contacts. When a registration is submitted or an approved registration is updated, the corresponding F1 contact record is created or updated automatically.

## Architecture

The integration follows the same pattern as the existing Xero, Accredo, QuickBooks, and Slack integrations:

* **Integration concern** (`FundraiserOneIntegration`) on Organisation — stores API key (encrypted via SimpleEncryption), base URL, and enabled flag in the `settings` hash column
* **API client** (`FundraiserOneClient`) — Faraday-based HTTP client authenticating via `SecretKey` header against `PUT /api/v1/Customer/`
* **Integration log** (`FundraiserOneLog`) — STI subclass of `IntegrationLog`, automatically visible in the admin integration logs UI
* **Sync job** (`FundraiserOne::SyncContactJob`) — builds the payload, calls upsert, stores the returned `ContactID` on the registration entity, and logs success/failure

## Payload Mapping

Each registration entity is synced as a single F1 contact. The payload is built by `FundraiserOnePayloadBuilder`:

**Fixed mappings** from User (account owner) and RegistrationEntity:

* `User.first_name` / `last_name` / `title` / `email` to `FirstName` / `LastName` / `Title` / `Email`
* `RegistrationEntity.name` to `Organisation`
* `RegistrationEntity.id` to `ReferenceId` (used for deduplication)

**Configurable mappings** from registration form fields — each form field can be assigned an F1 target field (e.g. `BusinessPhone`, `Email`, `Region`, `PostalAddressCity`) via the form builder UI. The builder reads all fields with a `fundraiser_one_field` value and maps their input values into the payload.

**Contact type** is set per registration type via the `fundraiser_one_contact_type` column (e.g. "Individual", "Organisation"), allowing orgs with multiple applicant types to send the correct value.

## Sync Triggers

| Event                                      | Behaviour                                                                                                                 |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| Registration submitted                     | `SyncContactJob` enqueued immediately                                                                                     |
| Approved registration updated (field edit) | `SyncContactJob` enqueued with a 30-second debounce (Redis `SET NX`) to coalesce rapid field edits into a single API call |

The debounce uses an atomic Redis key (`f1_sync_pending[org_id][entity_id]`) with a TTL. The first change sets the key and schedules a delayed job; subsequent changes within the window are no-ops. The job clears the key on execution so future edits can trigger a new sync.

## Per-Field Form Builder Mapping

Rather than configuring field mappings at the organisation level, each form field can be assigned an F1 target field directly in the form builder. A dropdown in the field context bar lets admins select which F1 field (if any) a form field maps to. This is stored as `fundraiser_one_field` on the field record.

## Organisation Setup

Configuration is done via Rails console:

```ruby
org = Organisation.find_by(internal_sub_domain: 'your-org-subdomain')
org.switch!

org.fundraiser_one_integration_enabled = true
org.fundraiser_one_api_key = 'your-f1-secret-key'
org.save!

# Set contact type per registration type
reg_type = org.registration_types.find_by(name: 'Your Type')
reg_type.update!(fundraiser_one_contact_type: 'Organisation')

# Verify connection
org.fundraiser_one_client.test_connection!
```

## Admin UI

* **Registration type edit modal**: shows a "FundraiserOne Contact Type" text field when the integration is enabled
* **Applicant show page** and **registered provider show page**: display the F1 Contact ID when present
* **Integration logs**: all sync attempts (success and failure) appear in admin settings > integration logs

## Error Handling

* API errors raise `FundraiserOneApiError`, logged to `FundraiserOneLog` and reported to Rollbar, then re-raised for Sidekiq retry
* Unexpected errors are logged and reported but not re-raised (to avoid infinite retry loops)
