Contacts are the people you message. Each workspace has an isolated contact graph with tags, custom fields, consent records, and channel addresses (email, phone, RCS identifiers) used for routing and compliance.

Privacy and storage

  • PII (name, email, phone) is encrypted at rest with AES-256-GCM and is workspace-scoped — no cross-tenant access.
  • Email can be indexed for deduplication via HMAC-SHA-256 hash; phone lookups use contact id after resolution.
  • PII fields are never exposed in analytics or webhook payloads beyond the workspace that owns them.

Contact record shape

{
  "id": "c_a1b2c3d4",
  "email": "jane@arowana.app",
  "phone": "+4917612345678",
  "first_name": "Jane",
  "last_name": "Doe",
  "status": "ACTIVE",
  "source": "API",
  "tags": ["newsletter", "vip", "migrated-2026-q1"],
  "custom_fields": {
    "loyalty_tier": "gold",
    "shop_id": "12345"
  },
  "consent_records": [
    {
      "id": "cr_a1b2c3",
      "channel_type": "EMAIL",
      "message_type": "NEWSLETTER",
      "status": "GRANTED",
      "source": "landing_page",
      "proof_text": "Opted in at arowana.app/subscribe — checkbox: I agree to receive the weekly newsletter",
      "enforced_doi": true,
      "doi_status": "DOI_ACCEPTED",
      "doi_channel": "EMAIL",
      "granted_at": "2026-01-15T10:35:00Z",
      "revoked_at": null,
      "created_at": "2026-01-15T10:30:00Z"
    },
    {
      "id": "cr_b2c3d4",
      "channel_type": "RCS",
      "message_type": "NEWSLETTER",
      "status": "GRANTED",
      "source": "csv_import",
      "proof_text": "Migrated from legacy platform — import batch import_2026-q1",
      "enforced_doi": false,
      "doi_status": null,
      "doi_channel": null,
      "granted_at": "2026-02-01T08:00:00Z",
      "revoked_at": null,
      "created_at": "2026-02-01T08:00:00Z"
    }
  ],
  "created_at": "2026-01-15T10:30:00Z",
  "updated_at": "2026-03-01T12:00:00Z"
}
Consent is per channel, per message type — not a single boolean flag. Each consent record in consent_records[] carries:
FieldMeaning
channel_typeEMAIL, RCS, or SMS
message_typeMESSAGE (action-based) or NEWSLETTER (subscription)
statusGRANTED — active. REVOKED — blocked. PENDING — DOI awaiting confirmation
sourceWhere consent came from: landing_page, csv_import, api, crm_sync
proof_textHuman-readable audit note — form URL, checkbox label, email subject
enforced_doitrue if Double Opt-In confirmation was required
doi_statusDOI_SEND (confirmation dispatched) or DOI_ACCEPTED (confirmed). null if DOI not enforced
doi_channelChannel used to deliver the DOI confirmation: EMAIL, RCS, SMS. null if no DOI
granted_atTimestamp when consent became GRANTED. null while still PENDING
revoked_atTimestamp when consent was revoked. null if still active
Enforcement: Sends to contacts without a GRANTED consent record for the relevant channel_type + message_type combination return 422. Opt-out events (unsubscribes, complaints) set the record to REVOKED immediately and block future sends for that combination.
Action-based (MESSAGE) sends do not require subscription consent — they’re for receipts, alerts, and verification flows. NEWSLETTER sends always require an explicit GRANTED consent record. Do not use MESSAGE to bypass consent requirements for promotional content.
Consent is managed through the dedicated Consent API — not via PATCH /v1/contacts/{id}. Use POST /v1/contacts/{id}/consent to grant consent and optionally trigger a DOI confirmation:
# Grant consent (single opt-in)
curl -X POST "https://api.arowana.app/v1/contacts/c_a1b2c3d4/consent" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "EMAIL",
    "message_type": "NEWSLETTER",
    "status": "GRANTED",
    "source": "api",
    "proof_text": "Opted in at checkout — pre-ticked newsletter checkbox"
  }'
# Start a DOI flow (status stays PENDING until contact confirms)
curl -X POST "https://api.arowana.app/v1/contacts/c_a1b2c3d4/consent" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_type": "EMAIL",
    "message_type": "NEWSLETTER",
    "status": "PENDING",
    "source": "landing_page",
    "proof_text": "Signed up at arowana.app/subscribe",
    "enforced_doi": true,
    "doi_channel": "EMAIL"
  }'
To revoke consent, call DELETE /v1/contacts/{id}/consent/{record_id}. The record is preserved for audit — status becomes REVOKED and revoked_at is stamped. Suppression reasons — HARD_BOUNCE, COMPLAINT, MANUAL_UNSUBSCRIBE — are written automatically by the platform when the corresponding event occurs and update the contact’s status field.

Segments and campaigns

Segments are stored filter definitions (saved queries) over contacts and their attributes. Campaigns use segments as audiences and advance executions per contact through each flow step. Segment size and estimated cost are shown before launch.

Imports and integrations

Contacts can be created via:
MethodAvailability
CSV import — field mapping and deduplication by email hashavailable
Shopify — real-time webhook sync + initial bulk importsoon
Landing page submissions — platform creates or updates contacts after form submissionsoon
Google Sheets — scheduled sync (hourly)soon
HubSpot — real-time webhook syncsoon
Salesforce — Streaming API syncsoon
Generic REST API — configurable interval syncsoon

Landing pages

Lead capture, DOI, and consent into contacts.

Consent sync path

Import, capture, and keep consent current across channels.

Campaigns

Audiences, segments, and per-contact execution.

Events & analytics

Engagement events tied to contacts.