Consent records are the legal basis for sending messages to a contact. Every contact can hold multiple consent records — one per channel and message type combination. Arowana enforces consent at send time: sending to a contact without GRANTED consent returns a 422.
{
  "id": "cr_a1b2c3",
  "contact_id": "c_a1b2c3d4",
  "channel_type": "EMAIL",
  "message_type": "NEWSLETTER",
  "status": "GRANTED",
  "source": "landing_page",
  "proof_text": "Opted in via signup form at shop.example.com/subscribe on 2026-01-10",
  "enforced_doi": true,
  "doi_status": "DOI_ACCEPTED",
  "doi_channel": "EMAIL",
  "granted_at": "2026-01-10T08:05:00Z",
  "revoked_at": null,
  "created_at": "2026-01-10T08:00:00Z"
}
channel_type
string
The channel this consent applies to: EMAIL, RCS, or SMS.
message_type
string
The send type this consent covers: MESSAGE (action-based) or NEWSLETTER (subscription).
status
string
Overall consent state.
ValueMeaning
GRANTEDConsent is active — sends are permitted
REVOKEDConsent was withdrawn — sends are blocked
PENDINGSingle opt-in recorded but DOI confirmation not yet completed
When enforced_doi: true, status only transitions to GRANTED after doi_status reaches DOI_ACCEPTED.
source
string
Where the consent signal came from (e.g. landing_page, api, checkout, crm_sync). Free-form string for your audit reference.
proof_text
string
Human-readable description of the consent event — form URL, checkbox label, email subject. Stored for GDPR audit. Max 5,000 characters.
enforced_doi
boolean
Whether Double Opt-In confirmation is required before this consent becomes GRANTED.
  • false (default) — single opt-in. Status moves directly to GRANTED on creation.
  • true — DOI required. Status stays PENDING until the contact confirms, at which point it advances to GRANTED and doi_status is set to DOI_ACCEPTED.
doi_status
string
Current DOI confirmation state. null when enforced_doi is false.
ValueMeaning
DOI_SENDDOI confirmation message has been sent to the contact via doi_channel
DOI_ACCEPTEDContact clicked the confirmation link — consent is now GRANTED
doi_channel
string
The channel used to deliver the DOI confirmation message: EMAIL, RCS, or SMS. null when enforced_doi is false.
granted_at
string
ISO 8601 timestamp when consent was granted (for DOI flows, this stamps when DOI_ACCEPTED is reached). null while PENDING.
revoked_at
string
ISO 8601 timestamp when consent was revoked. null if still active.

DOI lifecycle

When enforced_doi: true, consent follows a two-step confirmation process:
POST consent (enforced_doi: true)


  status: PENDING
  doi_status: DOI_SEND          ← platform sends confirmation via doi_channel

      │   contact clicks confirmation link

  status: GRANTED
  doi_status: DOI_ACCEPTED      ← granted_at stamped
Only GRANTED consent unlocks message sends. A contact in PENDING state cannot receive NEWSLETTER sends.

Endpoints

MethodPathDescription
GET/v1/contacts/{id}/consentList all consent records for a contact
POST/v1/contacts/{id}/consentCreate or update consent
DELETE/v1/contacts/{id}/consent/{record_id}Revoke a specific consent record

GET /v1/contacts//consent

Returns all consent records for a contact across all channels and message types.
Authorization
string
required
Bearer <api_key>
id
string
required
Contact ID.
curl "https://api.arowana.app/v1/contacts/c_a1b2c3d4/consent" \
  -H "Authorization: Bearer $API_KEY"
{
  "contact_id": "c_a1b2c3d4",
  "consent_records": [
    {
      "id": "cr_a1b2c3",
      "channel_type": "EMAIL",
      "message_type": "NEWSLETTER",
      "status": "GRANTED",
      "source": "landing_page",
      "proof_text": "Opted in via signup form at shop.example.com/subscribe",
      "enforced_doi": true,
      "doi_status": "DOI_ACCEPTED",
      "doi_channel": "EMAIL",
      "granted_at": "2026-01-10T08:05:00Z",
      "revoked_at": null,
      "created_at": "2026-01-10T08:00:00Z"
    },
    {
      "id": "cr_b2c3d4",
      "channel_type": "EMAIL",
      "message_type": "MESSAGE",
      "status": "GRANTED",
      "source": "checkout",
      "proof_text": "Checkout completion — implicit consent for order confirmations",
      "enforced_doi": false,
      "doi_status": null,
      "doi_channel": null,
      "granted_at": "2026-01-10T08:00:00Z",
      "revoked_at": null,
      "created_at": "2026-01-10T08:00:00Z"
    },
    {
      "id": "cr_c3d4e5",
      "channel_type": "EMAIL",
      "message_type": "NEWSLETTER",
      "status": "PENDING",
      "source": "landing_page",
      "proof_text": "Signed up at shop.example.com/subscribe — awaiting DOI",
      "enforced_doi": true,
      "doi_status": "DOI_SEND",
      "doi_channel": "EMAIL",
      "granted_at": null,
      "revoked_at": null,
      "created_at": "2026-03-28T09:00:00Z"
    }
  ]
}

POST /v1/contacts//consent

Creates or updates consent for a specific channel and message type. If a record for that combination already exists it is upserted.
Authorization
string
required
Bearer <api_key>
id
string
required
Contact ID.
channel_type
string
required
Channel to grant consent for: EMAIL, RCS, or SMS.
message_type
string
required
Send type to grant consent for: MESSAGE or NEWSLETTER.
status
string
required
GRANTED (single opt-in) or PENDING (start a DOI flow). When enforced_doi: true, always pass PENDING — the platform advances to GRANTED after confirmation.
source
string
required
Where consent was collected (e.g. "landing_page", "checkout", "crm_sync").
proof_text
string
Human-readable audit description. Include the form URL and checkbox label text. Max 5,000 characters.
enforced_doi
boolean
default:"false"
Set to true to require Double Opt-In confirmation before consent becomes GRANTED. When enabled, the platform sends a confirmation message to the contact via doi_channel and sets doi_status: DOI_SEND.
doi_channel
string
Required when enforced_doi: true. Channel used to deliver the DOI confirmation message: EMAIL, RCS, or SMS. The contact must have a verified address on this channel.
Consent is immediately GRANTED.
curl -sS -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": "MESSAGE",
    "status": "GRANTED",
    "source": "checkout",
    "proof_text": "Completed checkout at shop.example.com — order confirmation consent"
  }'
{
  "id": "cr_a1b2c3",
  "contact_id": "c_a1b2c3d4",
  "channel_type": "EMAIL",
  "message_type": "MESSAGE",
  "status": "GRANTED",
  "source": "checkout",
  "enforced_doi": false,
  "doi_status": null,
  "doi_channel": null,
  "granted_at": "2026-03-28T09:00:00Z",
  "revoked_at": null,
  "created_at": "2026-03-28T09:00:00Z"
}

DELETE /v1/contacts//consent/

Revokes a specific consent record. The record is not hard-deleted — status becomes REVOKED and revoked_at is stamped. Full audit trail is preserved. After revocation the contact is immediately suppressed from sends using that channel and message type.
Authorization
string
required
Bearer <api_key>
id
string
required
Contact ID.
record_id
string
required
Consent record ID (e.g. cr_a1b2c3).
curl -sS -X DELETE \
  "https://api.arowana.app/v1/contacts/c_a1b2c3d4/consent/cr_a1b2c3" \
  -H "Authorization: Bearer $API_KEY"
{
  "id": "cr_a1b2c3",
  "contact_id": "c_a1b2c3d4",
  "channel_type": "EMAIL",
  "message_type": "NEWSLETTER",
  "status": "REVOKED",
  "enforced_doi": true,
  "doi_status": "DOI_ACCEPTED",
  "doi_channel": "EMAIL",
  "revoked_at": "2026-03-28T10:00:00Z"
}

GDPR compliance notes

Arowana stores an ip_hash of the request IP automatically on every consent write. This provides a cryptographic audit fingerprint without storing raw IP addresses. You do not send this field.
  • Audit trail — consent records are never hard-deleted. Revocation stamps revoked_at and sets status: REVOKED. The full history is preserved.
  • DOI as best practice — use enforced_doi: true for NEWSLETTER consent in jurisdictions where double opt-in is required (e.g. Germany). For action-based (MESSAGE) consent tied to a transaction, single opt-in with proof_text is typically sufficient.
  • Send-time enforcement — only GRANTED records (with doi_status: DOI_ACCEPTED if DOI was enforced) permit sends. PENDING contacts cannot receive NEWSLETTER sends.
  • Cascading delete — deleting a contact cascade-deletes all consent records. Use status: BLOCKED on the contact instead if you need to suppress without losing audit history.

Consent sync journey

End-to-end guide for syncing opt-ins from a CRM or form platform.

Contact events

Webhook events fired on consent changes.