API reference / Contacts
Consent
Consent says which channels a contact agreed to hear from you on. It's tracked separately for calls and ringless voicemail, texts, and email. Use this page to check a contact's consent before you pick a channel, to record an opt-in your own form collected, and to see how opt-outs such as a STOP reply reach you. To stop all contact with a number or email whatever its consent, use the do-not-contact list. When a send fails on consent, its outcome code says why.
Routes
| Method | Route | Scope | What it does |
|---|---|---|---|
POST |
/contact/public/contacts/{contact_id}/consent |
consent:write |
Record an opt-in or an opt-out |
GET |
/contact/public/contacts/{contact_id}/consent |
consent:read |
List a contact's consent records |
PUT |
/contact/public/contacts/{contact_id}/consent/{consent_id}/revoke |
consent:write |
Revoke a contact's consent |
Check consent before you send
Get a contact returns a flag per channel. Read them before you choose how to reach someone.
| Channel | The contact agreed when | The contact opted out when | Record an opt-in with |
|---|---|---|---|
| Calls and ringless voicemail | has_tcpa_consent is true |
tcpa_opted_out_at is set |
tcpa_optin |
| Texts | has_sms_consent is true |
sms_opted_out_at is set |
sms_optin |
has_email_consent is true |
email_opted_out_at is set, or email_dnc is true |
email_optin |
Also check dnc. When it's true, one of the contact's phone numbers or their email was added to your do-not-contact list, and sends to a listed number or email are blocked. Check numbers or emails shows which.
Voice consent doesn't cover texts. A contact with has_tcpa_consent: true and has_sms_consent: false can get a ringless voicemail but not a text.
Use the flags to fall back to another channel. If a ringless voicemail fails because the mailbox is full, text the contact only if they agreed to texts:
function canText(contact) {
return contact.has_sms_consent === true && !contact.sms_opted_out_at && !contact.dnc;
}
function canEmail(contact) {
return contact.has_email_consent === true && !contact.email_opted_out_at && !contact.email_dnc;
}
If your account has Require TCPA Consent (PEWC) turned on in the app's consent settings, calls, ringless voicemails and texts to a contact without consent for that channel are blocked. Campaigns report them with outcome 6011. Other consent outcomes, such as 6005 for a revoked opt-in and 5014 to 5017 for email, are listed on Outcomes.
How opt-outs reach you
A STOP reply. When a contact texts STOP to one of your numbers, or taps an opt-out suggestion in an RCS message:
- Their number goes on your do-not-contact list with
method: "sms"andreason: "STOP keyword". You can't remove an entry the contact added themselves. - A
tcpa_optoutconsent record is saved withconsent_method: "sms". - The contact's
has_sms_consentandhas_tcpa_consentbecomefalse, andsms_opted_out_atandtcpa_opted_out_atare set. A STOP stops calls and ringless voicemails as well as texts. - You get a
contact.msg.opt_outwebhook with thephone_number, the text they sent insms_body, andopted_out_at. - If Universal Opt-Out (Revoke All) is on in your consent settings, the contact's email consent is revoked too.
An email unsubscribe. When a contact uses the unsubscribe link or header in one of your emails, their email goes on your do-not-contact list, email_dnc becomes true, email_issue_reason says why and email_opted_out_at is set. No webhook is sent, so read the contact's flags before you email them.
A revoke in the app or through this API. You get a contact.consent.revoked webhook. See Revoke a contact's consent for what changes.
Record consent
Saves a consent record for the contact and updates the contact's flags straight from it. Use it when your own form, call script or CRM collected the opt-in. Send the phone number or email the contact actually agreed on, plus the wording they saw, so the record holds up as proof.
| Field | Type | Required | Description |
|---|---|---|---|
consent_type |
string | Yes | One of the types below. |
phone_number |
string | One of phone_number or email |
The number the contact agreed on, in E.164 format. |
email |
string | One of phone_number or email |
The email the contact agreed on. |
consent_text |
string | No | The exact wording the contact agreed to. We store it with a SHA-256 hash. |
consent_method |
string | No | How you collected it, such as web_form or phone. Default api. |
consent_status |
string | No | granted (default) or revoked. Send revoked with an opt-out type. |
consent_version |
string | No | Version of your consent wording. Default 1.0.0. |
ip_address |
string | No | The contact's IP address when they agreed. Defaults to the IP of your API call, so send the contact's. |
user_agent |
string | No | The contact's browser. Defaults to your API call's User-Agent. |
trustedform_cert_url |
string | No | TrustedForm certificate URL. |
trustedform_token |
string | No | TrustedForm token. |
jornaya_lead_id |
string | No | Jornaya LeadiD. |
consent_type |
What it records |
|---|---|
tcpa_optin |
Agreed to calls and ringless voicemail. Sets has_tcpa_consent. |
sms_optin |
Agreed to texts. Sets has_sms_consent. |
sms_optin_confirmed |
Confirmed a texting opt-in, for example by replying to a double opt-in request. |
email_optin |
Agreed to email. Sets has_email_consent. |
esign |
Agreed to sign electronically. Sets has_esign_consent. |
web_tracking |
Agreed to website activity tracking. |
tcpa_optout |
Opted out of calls and ringless voicemail. |
sms_optout |
Opted out of texts. Also clears a confirmed texting opt-in. |
email_optout |
Opted out of email. Sets email_dnc with email_issue_reason: "unsubscribed". |
Recording email_optin clears an earlier unsubscribe: email_dnc goes back to false and the email comes off your do-not-contact list. Record it only when the contact has opted in again.
An opt-out type changes the contact's flags for that one channel. It doesn't add anything to the do-not-contact list, and it doesn't send a consent webhook.
curl -X POST "https://api-v2.dropcowboy.com/contact/public/contacts/756c1b7d-c27d-4faf-9365-bb83f0cbed66/consent" \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
-H "Content-Type: application/json" \
-d '{
"consent_type": "sms_optin",
"phone_number": "+13125550142",
"consent_method": "web_form",
"consent_text": "I agree to receive recurring texts from Example Co at the number above. Msg & data rates may apply. Reply STOP to opt out.",
"ip_address": "203.0.113.24",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)"
}'
The response is 201 with the new record in consent.
{
"data": {
"consent_id": "566bfa04-c530-4b01-afbd-e99f3564cbae",
"consent": {
"consent_id": "566bfa04-c530-4b01-afbd-e99f3564cbae",
"contact_id": "756c1b7d-c27d-4faf-9365-bb83f0cbed66",
"phone_number": "+13125550142",
"email": null,
"consent_type": "sms_optin",
"consent_status": "granted",
"consent_version": "1.0.0",
"consent_method": "web_form",
"consent_text": "I agree to receive recurring texts from Example Co at the number above. Msg & data rates may apply. Reply STOP to opt out.",
"consent_text_hash": "sha256:5d0c4f0e9c1b3a7f2e8d6c4b2a0f9e7d5c3b1a9f8e7d6c5b4a3f2e1d0c9b8a7f",
"ip_address": "203.0.113.24",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X)",
"third_party": {
"trustedform_cert_url": null,
"trustedform_token": null,
"jornaya_lead_id": null,
"custom_verification_id": null,
"custom_verification_url": null
},
"has_screenshot": false,
"granted_at": 1790870333466,
"revoked_at": null,
"created_at": 1790870333466
}
},
"meta": { "request_id": "35f86685-859f-4a8d-926a-45ec7fba7ddc" }
}
A grant sends a contact.consent.granted webhook.
List a contact's consent records
Returns every consent record for the contact, newest first, including revoked ones. Use it as the audit trail behind the contact's flags. A revoked record keeps its granted_at and has consent_status: "revoked" and a revoked_at.
curl "https://api-v2.dropcowboy.com/contact/public/contacts/756c1b7d-c27d-4faf-9365-bb83f0cbed66/consent" \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
{
"data": [
{
"consent_id": "631a390c-5d38-49b3-aaac-ca8f7201057e",
"contact_id": "756c1b7d-c27d-4faf-9365-bb83f0cbed66",
"phone_number": "+13125550142",
"email": null,
"consent_type": "tcpa_optin",
"consent_status": "granted",
"consent_method": "web_form",
"consent_text": "By clicking Submit, I agree that Example Co may call me at the number above, including with prerecorded messages.",
"ip_address": "203.0.113.24",
"granted_at": 1790783933466,
"revoked_at": null,
"created_at": 1790783933466
}
],
"meta": { "request_id": "23cf132a-adc9-4b91-b241-0199d62bc2e5" }
}
Each record has the same fields as the one Record consent returns. The sample is shortened.
Revoke a contact's consent
Use this when a contact tells you to stop. What changes depends on the record you name.
Revoking an email consent (email or email_optin) stops email only:
- Saves a new
email_optoutrecord for the contact. - Adds the contact's email to your do-not-contact list and clears their email consent.
- If Universal Opt-Out (Revoke All) or Sync Email Opt-Out to TCPA is on, the contact's phone consent is revoked too, as below.
Revoking any other record stops calls and texts:
- Saves a new
tcpa_optoutrecord for the contact. - Marks the contact's granted
tcpa_optinrecords as revoked. If the record you revoke isesign, every granted consent the contact has is revoked. - Adds every phone number on the contact to your do-not-contact list, and sets
dnc: true,has_tcpa_consent: falseandhas_sms_consent: false. - If the record you revoke is
web_tracking, deletes the contact's website activity. - If Universal Opt-Out (Revoke All) or Sync TCPA Opt-Out to Email is on, revokes the contact's email consent too.
- Sends a
contact.consent.revokedwebhook.
Both send a contact.consent.revoked webhook whose original_consent_id is the record you named. To withdraw texts only, record sms_optout instead.
| Field | Type | Required | Description |
|---|---|---|---|
consent_method |
string | No | How the contact asked, such as phone or email. Default api. |
ip_address |
string | No | Defaults to the IP of your API call. |
user_agent |
string | No | Defaults to your API call's User-Agent. |
curl -X PUT "https://api-v2.dropcowboy.com/contact/public/contacts/756c1b7d-c27d-4faf-9365-bb83f0cbed66/consent/631a390c-5d38-49b3-aaac-ca8f7201057e/revoke" \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
-H "Content-Type: application/json" \
-d '{ "consent_method": "phone" }'
The response is the new opt-out record.
{
"data": {
"consent_id": "ff356a80-6548-4144-9726-dcfdf02a0275",
"consent": {
"consent_id": "ff356a80-6548-4144-9726-dcfdf02a0275",
"contact_id": "756c1b7d-c27d-4faf-9365-bb83f0cbed66",
"phone_number": "+13125550142",
"email": null,
"consent_type": "tcpa_optout",
"consent_status": "revoked",
"consent_method": "phone",
"granted_at": null,
"revoked_at": 1790870396012,
"created_at": 1790870396012
}
},
"meta": { "request_id": "a4658481-13ce-4e5c-8392-c1f6d36bb15e" }
}
Errors
On these routes, the type URL in an error doesn't tell errors apart. Branch on status.
| Status | When | What to do |
|---|---|---|
400 |
consent_type is missing or not one of the types above. |
Send a type from Record consent. |
400 |
Recording without phone_number or email. |
Send the identifier the contact agreed on. |
400 |
Revoking a consent_id that doesn't exist (At least one identifier required). |
Get the id from List a contact's consent records. |
For everything else, see Responses, errors and limits.
While Drop Cowboy provides tools to support compliance efforts, customers remain solely responsible for obtaining proper consent, maintaining opt-out lists, and complying with all federal and state telemarketing regulations. Consult with your legal counsel to ensure your specific use case and consent mechanisms comply with applicable laws.