Overview
The IAC Members API provides a secure HTTPS endpoint used by EAA’s system to push individual membership updates directly into our Drupal site.
Each POSTed record is validated, queued, and then synced to the corresponding Drupal user account.
- 
Endpoint:
https://www.iac.org/api/iac/members - 
Auth: Bearer token (shared secret)
 - 
Queue:
iac_members_api_inbound(processed via cron) - 
Primary code:
/web/modules/custom/iac_members_api 
How It Works
- 
Inbound POST
- 
EAA posts JSON to
/api/iac/memberswithAuthorization: Bearer <TOKEN>. - 
The controller validates the token, checks IP allowlist (if defined), and enqueues the record.
 - 
A log entry “Accepted inbound member…” is recorded.
 
 - 
 - 
Queue Processing
- 
The queue worker (
InboundMemberWorker) runs every minute via system cron. - 
It calls
MemberSyncServiceto upsert the member record:- 
Finds existing user by IAC number, email, or username.
 - 
Updates changed fields (address, phone, email, expiration date, etc.).
 - 
Creates a new user if none exists.
 - 
Assigns the
current_memberrole. 
 - 
 - 
Logs each update as “Updated user Jane Doe (12345) fields: …” or “Created new user Jane Doe”.
 
 - 
 - 
Retry & Dead-Letter
- 
If processing fails, the item is retried up to 5 times.
 - 
After 5 failures, it is “dead-lettered” (error log entry).
These can be re-enqueued manually after the issue is fixed. 
 - 
 - 
Lapsed Members
- 
The nightly
lapsed.phpjob still controls expiration-based role changes. - 
The inbound API only ensures active/valid members are up to date.
 
 - 
 
Files & Key Components
| File | Purpose | 
|---|---|
iac_members_api.routing.yml | 
Defines /api/iac/members and /api/iac/ping routes. | 
MemberInboundController.php | 
Handles POSTs, validates token/IP, queues the record. | 
MemberSyncService.php | 
Applies data to Drupal user entities (upsert logic). | 
InboundMemberWorker.php | 
Queue worker that processes inbound items; includes retry/dead-letter logic. | 
iac_members_api.services.yml | 
Registers services (member_validator, member_sync). | 
settings.php | 
Stores secret token and optional IP allowlist. | 
Token Management (Bearer Authentication)
Where:web/sites/default/settings.php
$settings['iac_members_api_token'] = 'very-long-secret-token';To rotate the token:
- 
Generate a new token:
python3 - <<'PY' import secrets; print(secrets.token_urlsafe(64)) PY - 
Add it temporarily as
iac_members_api_token_next:$settings['iac_members_api_token_next'] = 'new-token-here'; - 
Update the controller’s auth check (already supports comparing multiple tokens).
Once EAA confirms the switch, remove the old token and rename the new one toiac_members_api_token. - 
Run
drush crto apply changes. 
IP Allowlist (optional)
Also in settings.php:
$settings['iac_members_api_ip_allowlist'] = [  '203.0.113.45',  '198.51.100.0/24', ];- 
If defined, only those IPs may POST.
 - 
Leave the array empty to allow any source.
 - 
CIDR notation supported.
 
Cron & Queue Processing
Location of cron job: webmaster user crontab
* * * * * sudo -u www-data /usr/local/bin/drush -r /usr/local/share/drupal9-prod -l www.iac.org queue:run iac_members_api_inbound 2>&1- 
Runs every minute.
 - 
Processes any queued items until the queue is empty.
 - 
Logs output to
/var/log/iac_members_queue.log. 
To check queue status manually
drush ev '$q=\Drupal::service("queue")->get("iac_members_api_inbound"); echo "Queue size: ".$q->numberOfItems()."\n";' To process immediately (manual run)
drush queue:run iac_members_api_inboundDebugging & Logs
Where to Look
- 
Drupal logs:
/admin/reports/dblogordrush ws --tailLook for
iac_members_apichannel. - 
System cron log:
/var/log/iac_members_queue.log 
Common Log Messages
| Message | Meaning | 
|---|---|
Accepted inbound member... | 
Record successfully received & queued. | 
Updated user Jane Doe (12345) fields: ... | 
Successful update. | 
Created new user Jane Doe | 
New user created. | 
Retry n for inbound member... | 
Processing failed; will retry. | 
Dead-lettered inbound member... | 
Gave up after 5 tries — manual review needed. | 
Manual Inspection Commands
# See user data drush user:information 12345 # View field value (example) drush php:eval '$u=\Drupal\user\Entity\User::load(12345); print_r($u->field_address2->value);' Making Edits or Improvements
To modify the logic (e.g., add new field mapping)
- 
Edit
/src/Service/MemberSyncService.php. - 
Map new field in both:
- 
processOne()($rowmapping) - 
updateUser()andcreateUser()(add to field sets) 
 - 
 - 
If adding a new Drupal field (e.g.,
field_member_type), create it via UI or Drush:drush field:create user field_member_type text --label="Member Type" - 
Clear caches:
drush cr 
To add new validation rules
Edit /src/Service/MemberValidator.php.
Troubleshooting Quick Reference
| Symptom | Likely Cause | Fix | 
|---|---|---|
| 404 (front page HTML) | Route cache not rebuilt | drush cr | 
| 401 Unauthorized | Wrong/missing Bearer token | Update token in settings.php | 
| Items never process | Cron not running | Check crontab -l, /var/log/iac_members_queue.log | 
| Repeated updates for same field | NULL/empty string mismatch | Confirm normalization in setIfDiff() | 
| “Dead-lettered inbound member…” | Permanent error in record | Inspect log, fix data or field, re-enqueue via Drush | 
| Class/service not found | Typo in iac_members_api.services.yml or cache not cleared | 
Fix YAML, run drush cr -v | 
Security Summary
- 
HTTPS enforced by nginx.
 - 
Bearer token authentication only (no cookies, no CSRF).
 - 
Token stored outside config (in
settings.php). - 
Optional IP allowlist.
 - 
Minimal route exposure (
POST /api/iac/membersonly). - 
Constant-time token comparison to prevent timing attacks.
 
Handy Commands Reference
# Clear caches and rebuild router
drush cr
# Run queue manually
drush qrun iac_members_api_inbound
# Watch logs live
drush ws --tail
# Check queue size
drush ev '$q=\Drupal::service("queue")->get("iac_members_api_inbound"); echo $q->numberOfItems()."\n";'
# Add or test cron job manually
sudo -u www-data drush -r /usr/local/share/drupal9-prod -l www.iac.org qrun iac_members_api_inbound
 
For New Webmasters
- 
If inbound updates stop, first check
dblogfor 401s or 5xx errors. - 
If queue backs up, run
drush qrun iac_members_api_inboundand tail logs. - 
If EAA rotates IPs or tokens, update
settings.phpanddrush cr. - 
To test connectivity, POST a sample JSON record with a known test member ID.
 - 
Don’t edit core files — everything lives in
web/modules/custom/iac_members_api/. 
    