Webhooks

Connect your membership data to other tools with Join It webhooks. Send real-time notifications for membership, payment, renewal, and check-in activity.

Use Join It webhooks to receive an HTTP notification when membership, payment, check-in, or registration data changes in your organization. Webhooks let your application react to changes without repeatedly polling Join It's API.

For each webhook automation, Join It sends an HTTP POST request to the endpoint URL you configure. The request body is JSON and contains the current resource data together with two routing fields:

  • webhook_object_type: the kind of object in the payload, such as memberships or payments.
  • webhook_event_type: the event subscription that produced the request, such as memberships.created.

Webhook deliveries can contain personal and financial data. Use an HTTPS endpoint and handle the payload as sensitive data.

Create a webhook

  1. Open Automations → Webhooks in Join It.
  2. If this is your first webhook, choose the option to create it. Otherwise, select Create Automation.
  3. Choose a trigger event.
  4. Enter the full, publicly reachable HTTPS URL for your receiver.
  5. Enter an internal name that will help your team identify the destination.
  6. Select Create Automation.

One automation subscribes one URL to one trigger. To receive several event types, create an automation for each trigger. The automations can all use the same endpoint URL; route each request using webhook_event_type.

Join It cannot send a webhook to localhost or another private address. During local development, expose your receiver through a secure tunnel or use an HTTPS request-inspection endpoint.

Available triggers

Trigger in Join Itwebhook_event_typewebhook_object_typePayloadWhen it is sent
Memberships Allmemberships.allmembershipsMembershipWhen a membership is created, updated, made inactive, renewed, or has its email changed. The event type remains memberships.all.
Memberships Createdmemberships.createdmembershipsMembershipAfter Join It creates a membership.
Memberships Inactivememberships.inactivemembershipsMembershipAfter a membership enters an inactive lifecycle state.
Memberships Updatedmemberships.updatedmembershipsMembershipAfter supported membership details change. This can also be sent as part of an email change, renewal, or inactive transition.
Memberships Renewedmemberships.renewalmembershipsMembershipAfter Join It completes a membership renewal. Note that the UI says “Renewed,” while the event value is memberships.renewal.
Payments Createdpayments.createdpaymentsPaymentAfter Join It creates a payment record.
Check-ins Allcheck_ins.allmemberships or check_insMembership or Check-inFor supported check-in lifecycle events. The event value remains check_ins.all; see the check-in payload note below.
Check-ins Createdcheck_ins.createdmembershipsMembershipAfter a check-in is created. The payload is the related Membership, not the Check-in record.
Check-ins Updatedcheck_ins.updatedcheck_insCheck-inIntended for changes to an existing Check-in record other than check-out. See the availability note below.
Check-ins Checked Outcheck_ins.checked_outcheck_insCheck-inAfter the checked_out value is added to a Check-in record.
Event Registrations Createdregistrations.createdregistrationsRegistrationAfter Join It creates a registration record.

Memberships Updated

The Memberships Updated trigger is used when supported membership details change, including:

  • membership type;
  • email address;
  • status;
  • external membership ID;
  • joined or expiration date;
  • profile information, such as address, company, job title, phone, date of birth, or gender; and
  • custom-field answers.

Because Join It can emit memberships.updated during other lifecycle operations, a receiver should compare the new object with its stored state rather than assume that only one field changed.

Check-in payload behavior

Check-in events do not all have the same payload shape:

  • check_ins.created sends the related Membership object. Its _id is the membership ID, not a check-in ID.
  • check_ins.updated and check_ins.checked_out send the Check-in record.
  • check_ins.all sends the corresponding shape for the underlying event. It can therefore have webhook_object_type: "memberships" or webhook_object_type: "check_ins".

When consuming check_ins.all, inspect webhook_object_type before parsing resource-specific fields. The current check_ins.all payload does not separately identify the underlying created, updated, or checked-out action.

Request format

Join It sends requests in this form:

POST /join-it/webhooks HTTP/1.1
Host: example.com
Content-Type: application/json

{ ...resource fields, "webhook_object_type": "memberships", "webhook_event_type": "memberships.updated" }

There is no outer envelope. Resource fields and webhook metadata are all at the top level of the JSON object.

Treat all fields other than the two webhook metadata fields as resource data. Fields can be null, optional fields can be absent, and Join It may add fields over time. A resilient receiver should ignore fields it does not recognize.

Membership example

{
  "_id": "mbr_8fK2sL4q",
  "organization_id": "org_2dP7xQ9n",
  "external_id": "MEM-1042",
  "email": "[email protected]",
  "display_name": "Alex Morgan",
  "membership_type": "type_annual",
  "membership_name": "Annual Member",
  "status": 100,
  "joined": "2026-01-15T10:30:00.000Z",
  "expiration_date": "2027-01-15T10:30:00.000Z",
  "profile": {
    "first_name": "Alex",
    "last_name": "Morgan",
    "company": null,
    "phone": "+44 20 7946 0958"
  },
  "custom_answers": [],
  "created": "2026-01-15T10:30:00.000Z",
  "updated": "2026-07-22T09:12:45.000Z",
  "webhook_object_type": "memberships",
  "webhook_event_type": "memberships.updated"
}

See the Memberships data model for field definitions and status values.

Payment example

Payment webhooks contain the current Payment object and an additional member_email field resolved from the associated account.

{
  "_id": "pay_7cN3vM8r",
  "organization_id": "org_2dP7xQ9n",
  "member_id": "acct_3yT9pB1k",
  "member_email": "[email protected]",
  "membership_ids": ["mbr_8fK2sL4q"],
  "channel": "stripe",
  "status": 100,
  "currency": "GBP",
  "subtotal": 5000,
  "total": 5000,
  "payment_date": "2026-07-22T09:12:45.000Z",
  "created": "2026-07-22T09:12:45.000Z",
  "updated": "2026-07-22T09:12:45.000Z",
  "webhook_object_type": "payments",
  "webhook_event_type": "payments.created"
}

See the Payments data model for the full object definition.

Check-in example

The following shape is used for check-in update and check-out deliveries. A created delivery uses the Membership shape shown above instead.

{
  "_id": "checkin_4hR8wS2j",
  "organization_id": "org_2dP7xQ9n",
  "membership_id": "mbr_8fK2sL4q",
  "channel": "kiosk",
  "note": null,
  "eventlisting_id": "event_6mJ1zA5c",
  "checked_in_by_user_id": "user_9qE2tV7b",
  "checked_out_by_user_id": "user_9qE2tV7b",
  "created": "2026-07-22T09:00:00.000Z",
  "checked_out": "2026-07-22T11:15:00.000Z",
  "webhook_object_type": "check_ins",
  "webhook_event_type": "check_ins.checked_out"
}

Registration example

{
  "_id": "reg_5vC1nD7p",
  "organization_id": "org_2dP7xQ9n",
  "app_name": "joinit",
  "channel": "portal",
  "membership_id": "mbr_8fK2sL4q",
  "members_id": "acct_3yT9pB1k",
  "registration_type_id": "regtype_8uF4kL2s",
  "event_id": "event_6mJ1zA5c",
  "email": "[email protected]",
  "order_id": "order_1aX6gH3m",
  "payment_id": "pay_7cN3vM8r",
  "created": "2026-07-22T09:12:45.000Z",
  "updated": "2026-07-22T09:12:45.000Z",
  "webhook_object_type": "registrations",
  "webhook_event_type": "registrations.created"
}

Acknowledge a delivery

Return any HTTP 2xx response to acknowledge successful receipt. A response body is not required. For example:

HTTP/1.1 204 No Content

Return the success response only after your application has durably recorded the event. For longer-running work, save the event to a queue and return promptly, then process it asynchronously.

Keep the configured endpoint URL current and point it directly at the receiver. Avoid redirects, and do not return a non-2xx response after successfully accepting an event.

Retries and duplicate events

Retry behavior currently varies by trigger:

Trigger familyDelivery attempts
Membership events, Payments Created, and Check-ins CreatedOne attempt
Check-ins All, Check-ins Updated, Check-ins Checked Out, and Registrations CreatedUp to three total attempts for a network error or non-2xx response

The retried events use exponential backoff with jitter, beginning at approximately one second.

Do not rely on a retry to recover an event. Join It does not guarantee exactly-once delivery or event ordering, and a retried request can produce duplicates. Make processing idempotent.

For most resource events, a practical deduplication key can include webhook_event_type, _id, and the resource's updated or created timestamp. Do not use that strategy for check_ins.created: its _id identifies the membership, so multiple check-ins for the same membership are not uniquely identified by the current payload.

Secure your receiver

Join It currently sends Content-Type: application/json but does not add a webhook signature, authorization header, delivery ID, or timestamp header.

Until signed requests are available:

  • use HTTPS only;
  • use a high-entropy, unguessable path for the receiver and treat the full endpoint URL as a secret;
  • do not expose the endpoint URL in client-side code or public logs;
  • validate organization_id, webhook_object_type, and webhook_event_type against the values your integration expects;
  • apply request-size limits and rate limiting at your edge;
  • process the minimum personal data needed for the integration; and
  • protect stored payloads and redact personal data from logs.

Because requests are not cryptographically signed, payload fields alone cannot prove that a request came from Join It. Do not use a webhook as the sole authorization for a high-risk action. When required, retrieve the referenced resource from Join It's authenticated API before taking that action.

Receiver example

This Express example validates the basic routing fields, records a deduplication key, and acknowledges the request before starting longer work. Replace the in-memory processedEvents set with durable storage in production.

import express from 'express';

const app = express();
const processedEvents = new Set();

app.use(express.json({ limit: '1mb' }));

app.post(
  '/join-it/webhooks/a-long-random-secret',
  function handleJoinItWebhook(req, res) {
    const payload = req.body;
    const eventType = payload?.webhook_event_type;
    const objectType = payload?.webhook_object_type;

    if (!eventType || !objectType || !payload?._id) {
      return res.status(400).json({ message: 'Invalid webhook payload' });
    }

    if (payload.organization_id !== process.env.JOIN_IT_ORGANIZATION_ID) {
      return res.status(403).json({ message: 'Unexpected organization' });
    }

    const resourceTimestamp = payload.updated || payload.created || 'unknown';
    const deduplicationKey = `${eventType}:${payload._id}:${resourceTimestamp}`;

    if (processedEvents.has(deduplicationKey)) {
      return res.sendStatus(204);
    }

    processedEvents.add(deduplicationKey);
    res.sendStatus(204);

    // Queue resource-specific work here. Route on eventType and objectType.
  }
);

app.listen(3000);

The sample deduplication key is not sufficient for check_ins.created because that payload does not currently contain a check-in ID.

Test a webhook

  1. Configure an HTTPS request-inspection endpoint or a receiver in a non-production environment.
  2. Create a webhook automation that points to it.
  3. Perform the selected action in a test organization.
  4. Confirm that your receiver sees an HTTP POST with Content-Type: application/json.
  5. Confirm the exact webhook_event_type, webhook_object_type, nullable fields, and resource shape.
  6. Return a 2xx response and verify that your handler records the event only once.
  7. Where retries apply, return a temporary non-2xx response and confirm that duplicate attempts are safe.

For membership webhooks, a membership record's automation section includes Re-send this Webhook. The current membership resend tool sends the record as memberships.created, regardless of the original automation trigger. Payment records offer a similar resend action for payments.created.

There is not currently an equivalent record-level resend tool for check-in or registration webhooks. Test those events by performing the corresponding action in a test organization.

See Testing Webhooks for the current Join It walkthrough.

Troubleshooting

No request arrives

  • Confirm the automation is active and belongs to the organization where you performed the action.
  • Confirm that the selected trigger matches the action. A Memberships Created subscription will not receive Payments Created events.
  • Confirm that the endpoint is public and uses a valid HTTPS certificate.
  • Check firewalls, allowlists, reverse proxies, and request-size limits.
  • Remember that Check-ins Updated is not currently emitted by normal check-in updates.

Join It receives a non-2xx response

  • Check your receiver logs for the response status and application error.
  • Ensure the route accepts POST requests and JSON bodies.
  • Return a 2xx only after the payload has been durably recorded.
  • Keep error responses concise; the response body is not used as an application-level reply.

The payload does not match the expected model

  • Route on both webhook_event_type and webhook_object_type.
  • For check_ins.created, expect a Membership object.
  • For check_ins.all, accept either a Membership or Check-in object.
  • Treat optional fields as nullable or absent.
  • Ignore new fields so additive schema changes do not break processing.

The same event is processed more than once

Webhook consumers must be idempotent. Store a durable deduplication record before applying side effects. Also check whether several Join It automations point the same trigger at the same endpoint.


Did this page help you?