Delete a Pass from a Klaviyo Flow

Daniel Baudino

Updated June 3, 2026

TL;DR: Permanently revoke a customer's pass when they unsubscribe, churn, or their membership expires.
  • Two methods: Webhook action (no code) or Code block (full control)
  • This is a hard delete — the pass is permanently removed from the customer's wallet
  • PassNinja automatically clears passninja_serial_number and sets passninja_has_pass to false on the Klaviyo profile after deletion

Overview

Use a delete flow to revoke a customer's pass when they churn, unsubscribe globally, or their membership lapses. The delete call uses the pass serial number stored on the Klaviyo profile. After the delete, PassNinja fires a Pass Deleted event back to Klaviyo which clears the pass properties from the profile automatically.

This is permanent. A deleted pass cannot be restored — the customer must be issued a new pass if they rejoin.

Prerequisites

RequirementDetails
PassNinja accountCustomer must have an active pass
Klaviyo integration connectedPassNinja → Integrations → Klaviyo must be connected
external_id on profileSet automatically to the pass serial after pass creation

You will need:

ValueWhere to find it
PASSNINJA_API_KEYPassNinja → Settings → API Keys
PASSNINJA_ACCOUNT_IDPassNinja → Settings → API Keys (format: aid_0x...)
PASSNINJA_TEMPLATE_IDPassNinja → Pass Templates → select template (format: ptk_0x...)

Method 1: Webhook Action (No Code)

Step 1 — Configure the request

FieldValue
MethodDELETE
URLhttps://api.passninja.com/v1/passes/ptk_0x4d5e6f/{{ person.external_id }}

Replace ptk_0x4d5e6f with your actual template ID.

No request body is needed for a delete.

Step 2 — Add headers

HeaderValue
X-API-KEYYour PassNinja API key
X-ACCOUNT-IDYour account ID (e.g. aid_0x1a2b3c)

Step 3 — Guard against profiles without a pass

Add a Profile Property condition before the Webhook action:

  • external_id is set

This prevents the webhook from firing for profiles that don't have a pass.

Suggested triggers

TriggerUse case
Unsubscribed from AllRevoke pass on global unsubscribe
Custom metric: Account ClosedCustomer-initiated account deletion
Custom metric: Membership ExpiredRemove pass when membership lapses
Removed from SegmentSegment exit triggers revocation

Method 2: Code Block

Step 1 — Set up environment variables

In the Code action's Environment Variables tab, add:

VariableValue
PASSNINJA_API_KEYYour PassNinja API key
PASSNINJA_ACCOUNT_IDYour account ID
PASSNINJA_TEMPLATE_IDYour pass template ID

Step 2 — Write the code

export const handler = async (event, profile, context) => {
  const attrs = profile.data.attributes;
  const serial = attrs.external_id;

  if (!serial) {
    return { skipped: true, reason: 'no pass found' };
  }

  const response = await fetch(
    `https://api.passninja.com/v1/passes/${process.env.PASSNINJA_TEMPLATE_ID}/${serial}`,
    {
      method: 'DELETE',
      headers: {
        'X-API-KEY': process.env.PASSNINJA_API_KEY,
        'X-ACCOUNT-ID': process.env.PASSNINJA_ACCOUNT_ID,
      },
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`PassNinja error: ${JSON.stringify(error)}`);
  }

  return { deleted: true };
};

Suggested triggers

TriggerUse case
Unsubscribed from AllRevoke pass on global unsubscribe
Custom metric: Account ClosedCustomer-initiated account deletion
Custom metric: Membership ExpiredRemove pass when membership lapses
Removed from SegmentSegment exit triggers revocation

What happens after deletion

PassNinja fires a Pass Deleted event to Klaviyo after the delete completes. The WalletPass Application Object record for this pass is updated automatically:

FieldValue after deletion
statusdeleted
updated_atTimestamp of the deletion

The external_id on the profile remains set to the serial — the profile is not deleted from Klaviyo. Any subsequent flow actions guarded by external_id is set will correctly skip the profile if you remove the external_id manually, or you can use the WalletPass object's status = deleted condition to filter.

Troubleshooting

Delete fails with 404

The serial in external_id may belong to a pass that was already deleted. The Code block's if (!serial) guard handles this gracefully. For the Webhook action, add the external_id is set condition before the action.

Pass still appears in the customer's wallet after deletion

Apple Wallet and Google Wallet remove the pass automatically when they next sync. This may take a few minutes depending on the device's connection. The pass cannot be used after deletion even if it's still visually present.


Support

Was this article helpful?
Yes No