FCHubFCHub.co

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/.

HookArgumentsWhen
grant_createdint $userId, int $planId, array $contextNew membership access granted
grant_revokedarray $grants, int $planId, int $userId, string $reasonAccess revoked
grant_expiredarray $grantAccess expired naturally
grant_term_expiredarray $grantAccess expired because membership term reached v1.3.0
grant_pausedarray $grant, string $reasonMembership paused
grant_resumedarray $grantPaused membership resumed
grant_renewedarray $grant, int $renewalCountSubscription renewed
trial_convertedarray $grant, int $planId, int $userIdTrial converted to paid
trial_expiredarray $grantTrial expired without payment
trial_expiring_soonarray $grant, int $daysLeftTrial approaching end
drip_unlockedarray $notification, array $grant, int $userIdDrip content unlocked
drip_milestone_reachedarray $grant, int $milestone, int $userIdDrip completion milestone (25/50/75/100%)
grant_expiring_soonarray $grant, int $daysLeftAccess approaching expiration
grant_anniversaryarray $grant, int $daysAnnual anniversary of grant date
payment_failedarray $affectedGrants, object $subscription, mixed $eventDataSubscription payment failed

Extension Actions

ActionArgumentsPurpose
fchub_memberships/resource_typesResourceTypeRegistry $registryRegister 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.

MethodEndpointDescription
GET/admin/plansList plans with optional filtering
POST/admin/plansCreate a plan
GET/admin/plans/optionsList compact plan options
GET/admin/plans/slug-previewPreview 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}/duplicateDuplicate a plan
GET/admin/plans/{id}/drip-scheduleGet the plan's drip schedule
GET/admin/plans/{id}/linked-productsList linked FluentCart products
POST/admin/plans/{id}/link-productLink a FluentCart product
DELETE/admin/plans/{id}/unlink-product/{feed_id}Remove a product feed link
GET/admin/plans/search-productsSearch FluentCart products
POST/admin/plans/resolve-resourcesResolve saved resource labels
GET/admin/plans/{id}/exportExport one plan
GET/admin/plans/export-allExport every plan
POST/admin/plans/importImport a plan definition
POST/admin/plans/{id}/scheduleSchedule 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

MethodEndpointDescription
GET/check-accessCheck a plan or WordPress resource for one user
GET/my-accessGet 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

MethodEndpointDescription
GET/admin/resource-typesGet available resource types
GET/admin/providersGet available providers
GET/admin/fluentcrm-tagsSearch FluentCRM tags
GET/admin/fluentcrm-listsSearch FluentCRM lists
GET/admin/fc-spacesSearch supported FluentCommunity spaces
GET/admin/fc-space-groupsSearch FluentCommunity space groups; returns an empty list when FluentCommunity is not active
GET/admin/fc-badgesCompatibility endpoint; returns no supported badge options

Reconciliation and provider recovery

MethodEndpointPermission and idempotency
GET/admin/integrations/fluentcrm/healthRequires manage_options
POST/admin/integrations/fluentcrm/reconcileA 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-reconciliationAccepts manage_fchub_memberships or manage_options
POST/admin/provider-reconciliation/repairAccepts 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.

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

EventWhen
grant_createdNew membership access granted
grant_revokedAccess revoked
grant_expiredAccess expired
grant_pausedMembership paused
grant_resumedMembership 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:

HeaderValue
Content-Typeapplication/json
X-FCHub-EventEvent type, such as grant_created
X-FCHub-DeliveryStable event ID from the envelope
X-FCHub-TimestampUTC occurred_at value from the envelope
X-FCHub-SignatureLowercase 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.

MethodEndpointPurpose
GET, POST/admin/webhooks/endpointsList endpoints or create a draft endpoint
POST/admin/webhooks/endpoints/{id}/secretGenerate or rotate an endpoint secret and return it once
POST/admin/webhooks/endpoints/{id}/testRun a one-shot test that never enters the retry queue
POST/admin/webhooks/endpoints/{id}/activateActivate an endpoint after its test succeeds
POST/admin/webhooks/endpoints/{id}/pausePause an active endpoint
DELETE/admin/webhooks/endpoints/{id}Delete an endpoint and cancel unfinished deliveries
GET/admin/webhooks/healthReturn off, needs_setup, ready, or degraded plus delivery counts
GET/admin/webhooks/deliveriesList redacted delivery history; supports page, per_page, and status
POST/admin/webhooks/deliveries/{id}/retryReset and schedule only a terminal failed delivery
POST/admin/webhooks/deliveries/{id}/cancelCancel a pending or retrying delivery
POST/admin/webhooks/testPersist a normal test event and attempt it through the production worker
POST/admin/settings/test-webhookCompatibility 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

AdapterProviderResource Types
WordPressContentAdapterwordpress_corePosts, pages, custom post types, taxonomies, menus, URLs, comments, special pages
FluentCommunityAdapterfluent_communityfc_space, fc_course
FluentCrmAdapterN/AContact tags and lists
LearnDashAdapterlearndashld_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_.

CSV Import

The import system supports multiple CSV formats through parser classes implementing CsvParserInterface.

Built-in Parsers

ParserDescription
GenericCsvParserStandard CSV with columns: email, plan_slug, status, starts_at, expires_at
PmproCsvParserCompatible with Paid Memberships Pro exports. Maps PMPro fields to membership fields

Import Flow

  1. Upload — CSV file is uploaded and validated
  2. Preview — The parser processes the CSV and returns a preview of what will be imported (users matched, plans matched, conflicts detected)
  3. 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

EventIntervalHandler
fchub_memberships_validity_check5 minutesSubscriptionValidityWatcher::check()
fchub_memberships_drip_processHourlyDripScheduleService::processNotifications()
fchub_memberships_expiry_notifyDailyAccessExpiringEmail::sendPendingNotifications()
fchub_memberships_daily_statsDailyMemberStatsReport::aggregateDaily() + anniversary check
fchub_memberships_trial_checkDailyTrialLifecycleService::sendTrialExpiringNotifications() + checkTrialExpirations()
fchub_memberships_plan_scheduleHourlyPlanService::processScheduledStatuses()
fchub_memberships_audit_cleanupWeeklyAuditLogRepository::cleanup(90)
fchub_memberships_webhook_reconcile5 minutesRecover due or interrupted durable webhook deliveries
fchub_memberships_webhook_cleanupDailyApply terminal webhook retention

Action Scheduler Hooks

HookHandler
fchub_memberships_send_emailwp_mail($to, $subject, $body, $headers)
fchub_memberships_deliver_webhookLoad 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.

On this page