Developer Reference
REST API endpoints, WP-CLI commands, action hooks, webhooks, adapter system, database schema, CSV import, and cron jobs.
This is the comprehensive developer reference for FCHub Memberships. It covers everything you need to extend, integrate with, or debug the plugin.
Action Hooks
The plugin fires these WordPress action hooks during the membership lifecycle. All hooks are prefixed with fchub_memberships/.
| Hook | Arguments | When |
|---|---|---|
grant_created | int $userId, int $planId, array $context | New membership access granted |
grant_revoked | array $grants, int $planId, int $userId, string $reason | Access revoked |
grant_expired | array $grant | Access expired naturally |
grant_term_expired | array $grant | Access expired because membership term reached v1.3.0 |
grant_paused | array $grant, string $reason | Membership paused |
grant_resumed | array $grant | Paused membership resumed |
grant_renewed | array $grant, int $renewalCount | Subscription renewed |
trial_converted | array $grant, int $planId, int $userId | Trial converted to paid |
trial_expired | array $grant | Trial expired without payment |
trial_expiring_soon | array $grant, int $daysLeft | Trial approaching end |
drip_unlocked | array $notification, array $grant, int $userId | Drip content unlocked |
drip_milestone_reached | array $grant, int $milestone, int $userId | Drip completion milestone (25/50/75/100%) |
grant_expiring_soon | array $grant, int $daysLeft | Access approaching expiration |
grant_anniversary | array $grant, int $days | Annual anniversary of grant date |
payment_failed | array $affectedGrants, object $subscription, mixed $eventData | Subscription payment failed |
Extension Actions
| Action | Arguments | Purpose |
|---|---|---|
fchub_memberships/resource_types | ResourceTypeRegistry $registry | Register custom resource types |
Listening to Hooks
add_action('fchub_memberships/grant_created', function (int $userId, int $planId, array $context) {
// Your custom logic when a membership is granted
$sourceType = $context['source_type'] ?? 'manual';
error_log("User {$userId} granted plan {$planId} via {$sourceType}");
}, 10, 3);REST API
All endpoints are under the fchub-memberships/v1 namespace. Administrative
requests from WordPress use cookie authentication, an X-WP-Nonce header, and
the capability stated below.
External membership writes use a WordPress Application Password over HTTPS.
The authenticated user needs manage_fchub_memberships (or manage_options),
and every Application Password write requires an Idempotency-Key header.
Reusing that key with the same request replays the stored result; reusing it
for different input returns a conflict.
The dedicated FCHub access key is deliberately narrower: it authenticates only
GET /check-access through the X-API-Key header. Query-string api_key
credentials are not accepted. Settings, webhook operations, and general
administration routes require manage_options; the membership mutation and
reconciliation exceptions are called out explicitly below. The read-only key
cannot call any admin route.
Unless a table or note says otherwise, every /admin route requires
manage_options. A same-origin WordPress request uses the logged-in cookie and
an X-WP-Nonce header. An external client may instead use WordPress Application
Password Basic authentication over HTTPS, with a user that has the stated
capability. General admin writes do not require Idempotency-Key; that header
is required only for the membership and reconciliation operations identified
below.
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/plans | List plans with optional filtering |
| POST | /admin/plans | Create a plan |
| GET | /admin/plans/options | List compact plan options |
| GET | /admin/plans/slug-preview | Preview the exact canonical slug and its availability |
| GET | /admin/plans/{id} | Get a plan with its rules |
| PUT, PATCH | /admin/plans/{id} | Update a plan |
| DELETE | /admin/plans/{id} | Delete a plan |
| POST | /admin/plans/{id}/duplicate | Duplicate a plan |
| GET | /admin/plans/{id}/drip-schedule | Get the plan's drip schedule |
| GET | /admin/plans/{id}/linked-products | List linked FluentCart products |
| POST | /admin/plans/{id}/link-product | Link a FluentCart product |
| DELETE | /admin/plans/{id}/unlink-product/{feed_id} | Remove a product feed link |
| GET | /admin/plans/search-products | Search FluentCart products |
| POST | /admin/plans/resolve-resources | Resolve saved resource labels |
| GET | /admin/plans/{id}/export | Export one plan |
| GET | /admin/plans/export-all | Export every plan |
| POST | /admin/plans/import | Import a plan definition |
| POST | /admin/plans/{id}/schedule | Schedule a plan status change |
Plan fields: title, slug, description, status, level, includes_plan_ids, restriction_message, redirect_url, duration_type, duration_days, trial_days, grace_period_days, settings, meta, scheduled_status, scheduled_at
Read-only and frontend API
| Method | Endpoint | Description |
|---|---|---|
| GET | /check-access | Check a plan or WordPress resource for one user |
| GET | /my-access | Get the logged-in user's membership account data |
/check-access accepts either plan=<slug> or
resource_type=<type>&resource_id=<id> (with optional
provider=wordpress_core), and identifies the user by exactly one of user_id
or email. A logged-in user may omit the identity for a self-check. External
clients send X-API-Key; the secure default limit is 300 verified-key requests
per 60 seconds. /my-access requires a logged-in WordPress user.
Dynamic Options
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/resource-types | Get available resource types |
| GET | /admin/providers | Get available providers |
| GET | /admin/fluentcrm-tags | Search FluentCRM tags |
| GET | /admin/fluentcrm-lists | Search FluentCRM lists |
| GET | /admin/fc-spaces | Search supported FluentCommunity spaces |
| GET | /admin/fc-space-groups | Search FluentCommunity space groups; returns an empty list when FluentCommunity is not active |
| GET | /admin/fc-badges | Compatibility endpoint; returns no supported badge options |
Reconciliation and provider recovery
| Method | Endpoint | Permission and idempotency |
|---|---|---|
| GET | /admin/integrations/fluentcrm/health | Requires manage_options |
| POST | /admin/integrations/fluentcrm/reconcile | A single-user request accepts manage_fchub_memberships or manage_options; scope=all requires manage_options. Application Password requests with dry_run=false require Idempotency-Key |
| GET | /admin/provider-reconciliation | Accepts manage_fchub_memberships or manage_options |
| POST | /admin/provider-reconciliation/repair | Accepts manage_fchub_memberships or manage_options and always requires Idempotency-Key |
External callers authenticate these routes with a WordPress Application
Password over HTTPS. Cookie-authenticated WordPress requests use
X-WP-Nonce. FluentCRM reconciliation defaults to dry_run=true; non-dry
Application Password calls use the same durable idempotency and replay contract
as membership writes. Provider repair requires an idempotency key for both
authentication modes.
WP-CLI Commands
All commands are under the wp fchub-membership namespace.
wp fchub-membership list-grants --member=<id|email> [--status=<status>] [--plan=<slug>]Lists grants for a user. Outputs a table with grant ID, plan, resource, status, and dates.
| Flag | Description |
|---|---|
--member | Required. User ID or email address |
--status | Filter by status: active, expired, revoked, paused |
--plan | Filter by plan slug |
wp fchub-membership grant --member=<id|email> --plan=<slug>Grants a full plan to a user, creating individual grants for every content rule in the plan.
| Flag | Description |
|---|---|
--member | Required. User ID or email address |
--plan | Required. Plan slug |
wp fchub-membership revoke --member=<id|email> --plan=<slug> [--reason=<text>]Revokes all grants for a plan from a user.
| Flag | Description |
|---|---|
--member | Required. User ID or email |
--plan | Required. Plan slug |
--reason | Revocation reason (recorded in audit log) |
wp fchub-membership revoke-by-order --order=<id> [--reason=<text>]Revokes all grants associated with a specific FluentCart order.
wp fchub-membership check --member=<id|email> --plan=<slug>Checks whether a user has active access to a plan. Outputs yes/no with details.
wp fchub-membership backfill [--dry-run] [--limit=<n>]Scans FluentCart orders and creates missing membership grants. Useful after initial plugin installation on an existing store.
wp fchub-membership expire-checkManually triggers the subscription validity check (same as the 5-minute cron).
wp fchub-membership drip-process [--limit=<n>]Manually processes pending drip notifications (same as the hourly cron).
wp fchub-membership purge-expired [--days=<n>] [--dry-run]Removes expired/revoked grants older than N days.
wp fchub-membership debug --member=<id|email>Outputs detailed debug information about a user's membership state, grants, subscriptions, and access evaluation results.
wp fchub-membership stats [--period=<period>] [--aggregate]Shows overview statistics. Pass --aggregate to force daily stats aggregation.
wp fchub-membership export-members [--plan=<slug>] [--status=<status>] [--format=<csv|json|table>]Exports member data. Supports CSV, JSON, and WP-CLI table formats.
Webhooks
Webhooks send durable membership events to external services as signed HTTP POST requests with JSON bodies. Delivery is at-least-once: receivers must deduplicate by the stable event ID and must not assume exactly-once delivery.
Configuration
In Memberships > Settings > Webhooks & API:
- Create up to ten named endpoints. Production destinations require HTTPS and must resolve only to public addresses.
- Generate an independent signing secret for each endpoint. It is shown once; later responses expose only whether it is configured.
- Send a one-shot test through the endpoint's real signing and network-safety path. The endpoint cannot activate until that test succeeds.
- Activate, pause, rotate, or delete each endpoint independently.
- Generate the read-only access key separately. It is also shown once, stored as a password hash, and never returned by settings reads.
Copy either credential before acknowledging its dialog. There is no “show it again” endpoint, because secrets are traditionally less useful when treated as decorative settings copy.
Events
| Event | When |
|---|---|
grant_created | New membership access granted |
grant_revoked | Access revoked |
grant_expired | Access expired |
grant_paused | Membership paused |
grant_resumed | Membership resumed |
Payload Format
{
"id": "8f14b86a-b3ec-4fe5-a8f7-8a01b694bf15",
"schema_version": "1.0",
"event_type": "grant_created",
"occurred_at": "2026-07-22T08:00:00+00:00",
"site_url": "https://example.com",
"data": {
"user": {
"id": 42,
"email": "member@example.com",
"display_name": "Jane Doe"
},
"plan": {
"id": 1,
"title": "Pro Membership",
"slug": "pro"
},
"context": {
"source_type": "order",
"source_id": 156
}
}
}Signature Verification
Every webhook includes these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-FCHub-Event | Event type, such as grant_created |
X-FCHub-Delivery | Stable event ID from the envelope |
X-FCHub-Timestamp | UTC occurred_at value from the envelope |
X-FCHub-Signature | Lowercase hexadecimal HMAC-SHA256 of the exact raw body |
The signature contract is:
signature = HMAC-SHA256(request_body, webhook_secret)Verify the signature on your receiving end:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_FCHUB_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, $your_shared_secret);
if (!hash_equals($expected, $signature)) {
http_response_code(403);
exit('Invalid signature');
}
$event = json_decode($payload, true, flags: JSON_THROW_ON_ERROR);
$delivery = $_SERVER['HTTP_X_FCHUB_DELIVERY'] ?? '';
$eventType = $_SERVER['HTTP_X_FCHUB_EVENT'] ?? '';
$timestamp = $_SERVER['HTTP_X_FCHUB_TIMESTAMP'] ?? '';
if (!hash_equals((string) $event['id'], $delivery)
|| !hash_equals((string) $event['event_type'], $eventType)
|| !hash_equals((string) $event['occurred_at'], $timestamp)
) {
http_response_code(400);
exit('Invalid webhook metadata');
}Verify the HMAC against the untouched request bytes before decoding the JSON. Then verify delivery ID, event type, and timestamp against the envelope. Record the delivery ID durably before applying side effects, and return a 2xx response only after that record and the intended effect are safe to acknowledge.
Delivery
The plugin persists one immutable event and one delivery per unique destination
before scheduling fchub_memberships_deliver_webhook. Action Scheduler is
preferred; WP-Cron provides the scheduling fallback. Scheduled arguments
contain only the delivery ID.
The worker re-reads the stored event and current secret, signs the exact stored
body, and sends it through WordPress safe HTTP with a 15-second timeout and at
most three redirects. Every transport error or non-2xx response is a failed
attempt. Retry delays after the first attempt are
60, 300, 1800, 7200, 21600, 86400 seconds, for seven total attempts. Valid
Retry-After values on HTTP 429 and 503 are honoured up to 86,400 seconds.
Successful deliveries are retained for 30 days and terminal failures for 90 days. Pending, processing, and retrying rows are not purged. Stored response bodies are bounded, and history never exposes the event body or signing secret.
Operations
The WordPress admin calls these routes with cookie authentication and an
X-WP-Nonce header. Their permission check is always manage_options; the
read-only key and manage_fchub_memberships capability do not grant access.
| Method | Endpoint | Purpose |
|---|---|---|
| GET, POST | /admin/webhooks/endpoints | List endpoints or create a draft endpoint |
| POST | /admin/webhooks/endpoints/{id}/secret | Generate or rotate an endpoint secret and return it once |
| POST | /admin/webhooks/endpoints/{id}/test | Run a one-shot test that never enters the retry queue |
| POST | /admin/webhooks/endpoints/{id}/activate | Activate an endpoint after its test succeeds |
| POST | /admin/webhooks/endpoints/{id}/pause | Pause an active endpoint |
| DELETE | /admin/webhooks/endpoints/{id} | Delete an endpoint and cancel unfinished deliveries |
| GET | /admin/webhooks/health | Return off, needs_setup, ready, or degraded plus delivery counts |
| GET | /admin/webhooks/deliveries | List redacted delivery history; supports page, per_page, and status |
| POST | /admin/webhooks/deliveries/{id}/retry | Reset and schedule only a terminal failed delivery |
| POST | /admin/webhooks/deliveries/{id}/cancel | Cancel a pending or retrying delivery |
| POST | /admin/webhooks/test | Persist a normal test event and attempt it through the production worker |
| POST | /admin/settings/test-webhook | Compatibility alias for /admin/webhooks/test |
Send a production-path test from Memberships > Settings or via REST:
curl -X POST https://example.com/wp-json/fchub-memberships/v1/admin/webhooks/test \
-H "Cookie: wordpress_logged_in_..." \
-H "X-WP-Nonce: <nonce>"Rotating one endpoint secret pauses that endpoint and requires a fresh successful test before reactivation. A terminal failed delivery may be retried after rotation and will be signed with the endpoint's current secret. Deleting an endpoint cancels its unfinished deliveries while preserving terminal history.
Adapter System
The adapter system provides a uniform interface for granting and revoking access across different content providers.
AccessAdapterInterface
interface AccessAdapterInterface
{
public function supports(string $resourceType): bool;
public function grant(int $userId, string $resourceType, string $resourceId, array $context = []): array;
public function revoke(int $userId, string $resourceType, string $resourceId, array $context = []): array;
public function check(int $userId, string $resourceType, string $resourceId): bool;
public function getResourceLabel(string $resourceType, string $resourceId): string;
}Built-in Adapters
| Adapter | Provider | Resource Types |
|---|---|---|
WordPressContentAdapter | wordpress_core | Posts, pages, custom post types, taxonomies, menus, URLs, comments, special pages |
FluentCommunityAdapter | fluent_community | fc_space, fc_course |
FluentCrmAdapter | N/A | Contact tags and lists |
LearnDashAdapter | learndash | ld_course, ld_group |
Registering Custom Adapters
Adapters are resolved by provider string. You can register custom resource types through the fchub_memberships/resource_types action:
add_action('fchub_memberships/resource_types', function ($registry) {
$registry->register('my_resource', [
'label' => 'My Custom Resource',
'group' => 'content',
'provider' => 'my_plugin',
'searchable' => true,
]);
});Database Tables
All tables are prefixed with {wp_prefix}fchub_membership_.
| Column | Type | Description |
|---|---|---|
id | BIGINT | Primary key |
title | VARCHAR(255) | Plan name |
slug | VARCHAR(100) | URL-safe slug (unique) |
description | TEXT | Plan description |
status | VARCHAR(20) | active, draft, inactive |
level | INT | Hierarchy level (0 = lowest) |
includes_plan_ids | LONGTEXT | JSON array of included plan IDs |
restriction_message | TEXT | Custom restriction message |
redirect_url | VARCHAR(500) | Redirect URL for non-members |
duration_type | VARCHAR(30) | lifetime, fixed_days, subscription_mirror, fixed_anchor |
duration_days | INT | Days for fixed duration |
trial_days | INT | Trial period in days |
grace_period_days | INT | Grace period in days |
settings | LONGTEXT | JSON settings blob |
meta | LONGTEXT | JSON metadata |
scheduled_status | VARCHAR(20) | Future status change |
scheduled_at | DATETIME | When to apply the status change |
created_at | TIMESTAMP | Created timestamp |
updated_at | TIMESTAMP | Updated timestamp |
| Column | Type | Description |
|---|---|---|
id | BIGINT | Primary key |
plan_id | BIGINT | FK to plans (CASCADE delete) |
provider | VARCHAR(50) | wordpress_core, learndash, fluent_community |
resource_type | VARCHAR(50) | post, page, category, fc_space, etc. |
resource_id | VARCHAR(100) | Specific resource ID or * for wildcard |
drip_delay_days | INT | Days to delay (for delayed drip type) |
drip_type | VARCHAR(20) | immediate, delayed, fixed_date |
drip_date | DATETIME | Fixed unlock date |
sort_order | INT | Display order |
meta | LONGTEXT | JSON metadata |
| Column | Type | Description |
|---|---|---|
id | BIGINT | Primary key |
user_id | BIGINT | WordPress user ID |
plan_id | BIGINT | FK to plans (SET NULL on delete) |
provider | VARCHAR(50) | Content provider |
resource_type | VARCHAR(50) | Resource type |
resource_id | VARCHAR(100) | Resource ID |
source_type | VARCHAR(30) | order, subscription, automation, manual, import |
source_id | BIGINT | FluentCart order/subscription ID |
feed_id | BIGINT | Integration feed ID |
grant_key | VARCHAR(64) | Unique idempotency key |
status | VARCHAR(20) | active, paused, revoked, expired |
starts_at | DATETIME | When access starts |
expires_at | DATETIME | When access expires (null = lifetime) |
drip_available_at | DATETIME | When drip content unlocks |
trial_ends_at | DATETIME | Trial end date |
source_ids | LONGTEXT | JSON array of all source IDs |
cancellation_requested_at | DATETIME | When cancellation was requested |
cancellation_effective_at | DATETIME | When cancellation takes effect |
cancellation_reason | VARCHAR(500) | Cancellation reason |
renewal_count | INT | Number of subscription renewals |
meta | LONGTEXT | JSON metadata |
| Column | Type | Description |
|---|---|---|
id | BIGINT | Primary key |
entity_type | VARCHAR(30) | grant, plan, rule |
entity_id | BIGINT | ID of the affected entity |
action | VARCHAR(30) | created, updated, revoked, expired, etc. |
actor_id | BIGINT | User ID who performed the action (0 = system) |
actor_type | VARCHAR(20) | user, system, cron, automation |
old_value | LONGTEXT | JSON of previous state |
new_value | LONGTEXT | JSON of new state |
context | VARCHAR(255) | Additional context string |
created_at | TIMESTAMP | When the action occurred |
Entries older than 90 days are cleaned up weekly by the fchub_memberships_audit_cleanup cron.
CSV Import
The import system supports multiple CSV formats through parser classes implementing CsvParserInterface.
Built-in Parsers
| Parser | Description |
|---|---|
GenericCsvParser | Standard CSV with columns: email, plan_slug, status, starts_at, expires_at |
PmproCsvParser | Compatible with Paid Memberships Pro exports. Maps PMPro fields to membership fields |
Import Flow
- Upload — CSV file is uploaded and validated
- Preview — The parser processes the CSV and returns a preview of what will be imported (users matched, plans matched, conflicts detected)
- Execute — Grants are created for each valid row. Existing grants are updated if found
Custom Parsers
Implement CsvParserInterface and register your parser via a hook to support additional CSV formats from other membership plugins.
Cron Jobs
WP-Cron Events
| Event | Interval | Handler |
|---|---|---|
fchub_memberships_validity_check | 5 minutes | SubscriptionValidityWatcher::check() |
fchub_memberships_drip_process | Hourly | DripScheduleService::processNotifications() |
fchub_memberships_expiry_notify | Daily | AccessExpiringEmail::sendPendingNotifications() |
fchub_memberships_daily_stats | Daily | MemberStatsReport::aggregateDaily() + anniversary check |
fchub_memberships_trial_check | Daily | TrialLifecycleService::sendTrialExpiringNotifications() + checkTrialExpirations() |
fchub_memberships_plan_schedule | Hourly | PlanService::processScheduledStatuses() |
fchub_memberships_audit_cleanup | Weekly | AuditLogRepository::cleanup(90) |
fchub_memberships_webhook_reconcile | 5 minutes | Recover due or interrupted durable webhook deliveries |
fchub_memberships_webhook_cleanup | Daily | Apply terminal webhook retention |
Action Scheduler Hooks
| Hook | Handler |
|---|---|
fchub_memberships_send_email | wp_mail($to, $subject, $body, $headers) |
fchub_memberships_deliver_webhook | Load one durable delivery ID and send it with wp_safe_remote_post() |
Lifecycle cron handlers check for FLUENTCART_VERSION before using FluentCart.
Webhook reconciliation and cleanup are scheduled only after schema 1.8.0 and
the durable webhook tables pass their readiness checks.
Reports & Analytics
Understand member growth, plan performance, churn, revenue, renewals, trials, content interest, and retention without exporting the entire universe.
Troubleshooting
Start with the Memberships workspace, follow the visible issue, and fix common access, content, email, provider, API, and webhook problems.