Webhooks

Learn how to receive real-time notifications when events occur in your Pelcro account.

Quickstart

Get up and running with Pelcro webhooks in three steps:

1. Create an endpoint that accepts POST requests and returns a 200 status code:

// PHP
// Laravel
Route::post('/webhooks/pelcro', function (Request $request) {
    $event = $request->all();
    Log::info("Received {$event['type']} event: {$event['id']}");

    // Acknowledge receipt immediately
    // Process the event asynchronously (e.g., dispatch a queued job)
    ProcessWebhookEvent::dispatch($event);

    return response('OK', 200);
});
# Flask
@app.route('/webhooks/pelcro', methods=['POST'])
def handle_webhook():
    event = request.get_json()
    print(f"Received {event['type']} event: {event['id']}")

    # Acknowledge receipt immediately
    # Process the event asynchronously (e.g., enqueue a task)
    process_event.delay(event)

    return 'OK', 200
// Express
app.post('/webhooks/pelcro', (req, res) => {
  const event = req.body;
  console.log(`Received ${event.type} event: ${event.id}`);

  // Acknowledge receipt immediately
  res.status(200).send('OK');

  // Process the event asynchronously
  processEvent(event);
});

2. Register your URL in the Pelcro Platform under Settings > Notifications. Create a new webhook endpoint, enter your URL, then select the events you want to receive.

3. Verify signatures to confirm events are from Pelcro (see Signature Verification below).

That's it. Pelcro will now send an HTTP POST request to your endpoint every time a subscribed event occurs.


Configuration

You can register webhook endpoints from Settings > Notifications in the Pelcro Platform. Although you can subscribe to all events, this is not recommended as it will put unnecessary pressure on your server. Configure your webhook to listen to only the events required by your integration. You can also subscribe to webhooks via the API.

When registering the URL, it must respond with successful status code (2xx) to the POST requests in order to pass the initial validation.

Once you register a webhook URL with Pelcro, we will issue an HTTP POST request to the URL specified every time that event occurs, and your endpoint is expected to answer in a timely fashion by returning a successful status code (2xx). Any other status code is considered a failure.

Payload Structure

Every webhook event follows the same envelope structure:

{
    "type": "subscription.created",
    "id": "evt_ybR5i5QwSVvoWy28ndD4q6hA",
    "created": 1542034347,
    "data": {
        "object": {
            "object": "subscription",
            "id": 6807,
            "user_id": 40864,
            "plan_id": 386,
            "cancel_at_period_end": false,
            "canceled_at": null,
            "current_period_end": "2024-02-01T00:00:00.000000Z",
            "current_period_start": "2024-01-01T00:00:00.000000Z"
        }
    }
}
FieldTypeDescription
typestringThe event type (e.g., subscription.created).
idstringA unique event identifier (format: evt_ + 24 characters). Use this for idempotency — if your endpoint receives the same id twice, you can safely skip the duplicate.
createdintegerUnix timestamp of when the event was created.
data.objectobjectThe resource that triggered the event. The object field inside identifies the resource type.

For events that include change tracking (e.g., subscription.changed, customer.updated), the payload also includes a data.previous_attributes object with the field values before the change.

The webhook is sent with the following headers:

Content-Type: application/json
X-Pelcro-Hmac-SHA256: pdvcCMQA5298lNhqp7i52StRjHEKgY7Z77yU+f+lfGQ=

Events Ordering

The order in which events are delivered to endpoints is not related to the order they were triggered on our system. For example, the logical flow of events triggered while creating a subscription might be the following:

  • subscription.created
  • invoice.created
  • charge.created

Your integration shouldn't assume delivery of events in the order specified in the example above, and needs to handle processing accordingly.


Delivery and Retries

Timeouts

Your endpoint must respond quickly. Pelcro enforces the following timeouts:

DescriptionTimeout
Maximum time for your endpoint to accept the connection5 seconds
Maximum time for your endpoint to return a successful status code (2xx)20 seconds

Your endpoint should acknowledge receipt of the event immediately by returning a 2xx status code, then process the event asynchronously in a background job or queue. Do not perform business logic (API calls, database operations, emails) before responding.

Retry Behavior

If your endpoint fails to return a 2xx status code, Pelcro will retry the delivery up to 3 attempts. Retries are processed immediately through our queue system with no delay between attempts. After all attempts are exhausted, the delivery is marked as permanently failed.

🚧

No exponential backoff

Retries happen in quick succession. If your endpoint is temporarily down, all 3 attempts may fail before it recovers. Make sure your endpoint is highly available, and monitor your webhook delivery statuses in the Pelcro Platform.

Statuses

Various circumstances can cause webhook event delivery to enter a specific status. Refer to the table below for information on each status.

Webhook statusDescription
OKThe endpoint returned a successful status code (2xx).
ERR - 3xxThe endpoint attempted to redirect the request to another location, which is not handled and thus is considered a failure.
ERR - 4xxThe endpoint can't or won't process the request. This might occur when the server detects an error (400), when it has access restrictions (401, 403), or when the endpoint URL doesn't exist (404).
ERR - 5xxThe endpoint encountered an error while processing the request.
ERR - TLSA secure connection could not be established to the endpoint. These errors can be caused by an issue with the SSL/TLS certificate or an intermediate certificate in the destination server's certificate chain. Performing a SSL server test might help identifying the cause of this error.
ERR - Unable to connectA connection with the endpoint could not be established. This might happen if the endpoint cannot be resolved, or if the destination server did not accept the connection in a timely manner.
ERR - Timed outThe endpoint did not send back a successful status code (2xx) in a timely manner.
ERRAn unspecified, unhandled or unrecognized error occurred.

Best Practices

  • Return 2xx immediately, then process events in a background queue.
  • Use the id field for idempotency. Your endpoint may receive the same event more than once. Store processed event IDs and skip duplicates.
  • Subscribe only to events you need. This reduces load on your server and simplifies your integration.
  • Monitor delivery statuses in the Pelcro Platform to catch endpoint failures early.

Signature Verification

Pelcro signs every webhook event by including an HMAC-SHA256 signature in the X-Pelcro-Hmac-SHA256 header. This allows you to verify that events were sent by Pelcro, not by a third party.

How it works

  1. Extract the base64-encoded signature from the X-Pelcro-Hmac-SHA256 header.
  2. Compute an HMAC-SHA256 hash using your webhook signing secret as the key and the raw JSON request body as the message.
  3. Compare your computed signature with the one from the header.
  4. Return a 2xx status code to acknowledge receipt. Pelcro will retry up to 3 times on failure.

Code Examples

// PHP

$payload = file_get_contents('php://input');
$secret  = 'your_webhook_secret';

$expected = base64_encode(
    hash_hmac('sha256', $payload, $secret, true)
);

$signature = $_SERVER['HTTP_X_PELCRO_HMAC_SHA256'] ?? '';

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

http_response_code(200);

// Process the event
$event = json_decode($payload, true);
const crypto = require('crypto');

app.post('/webhooks/pelcro', (req, res) => {
  const payload   = JSON.stringify(req.body);
  const secret    = 'your_webhook_secret';
  const signature = req.headers['x-pelcro-hmac-sha256'];

  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('base64');

  if (expected !== signature) {
    return res.status(401).send('Invalid signature');
  }

  res.status(200).send('OK');

  // Process the event
  const event = req.body;
});
import hmac
import hashlib
import base64

payload   = request.get_data()  # raw request body
secret    = b'your_webhook_secret'
signature = request.headers.get('X-Pelcro-Hmac-SHA256', '')

expected = base64.b64encode(
    hmac.new(secret, payload, hashlib.sha256).digest()
).decode()

if not hmac.compare_digest(expected, signature):
    abort(401, 'Invalid signature')

return 'OK', 200
require 'openssl'
require 'base64'

payload   = request.body.read
secret    = 'your_webhook_secret'
signature = request.env['HTTP_X_PELCRO_HMAC_SHA256']

expected = Base64.strict_encode64(
  OpenSSL::HMAC.digest('sha256', secret, payload)
)

unless Rack::Utils.secure_compare(expected, signature)
  halt 401, 'Invalid signature'
end

status 200
🚧

Use constant-time comparison

Always use a constant-time string comparison function (like hash_equals in PHP, hmac.compare_digest in Python, or Rack::Utils.secure_compare in Ruby) to prevent timing attacks.


Events

Below is a complete list of all events we currently send to your configured webhook endpoint. Select an event's resource page to see the full JSON payload. Please note that we may change, delete, or add more at any time. While developing and maintaining your code, keep an eye on the changelog.

EventDescription
address.createdA new address is created and added to a customer's account.
address.updatedAn existing address is updated or modified on a customer's account.

EventDescription
bill.createdA new bill is created for a vendor.
bill.finalizedA draft bill is finalized and moved to open status.
bill.paidA bill is marked as paid.
bill.updatedA bill is updated.
bill.deletedA bill is deleted.

EventDescription
campaign.triggeredA campaign with a notification action has started processing.
campaign.run_completedA campaign run has completed processing all matching subscriptions.

EventDescription
charge.failedA charge attempt on a customer's payment method has failed.
charge.refundedA charge has been refunded (fully or partially).
charge.succeededA customer's payment method has been successfully charged.

EventDescription
customer.createdA new customer registers and a user record is created.
customer.updatedA customer's profile information is updated.
customer.deletedA customer record is deleted.
customer.PasswordResetRequestSubmittedA customer requests a password reset. Contains the password reset token.
customer.password_update.succeededA customer successfully updates their password.
customer.email_verification_requestA customer is requested to verify their email after account creation.
customer.email_verification_succeededA customer successfully verifies their email.
customer.passwordless_login_requestA customer requests to log in via passwordless login.

EventDescription
export.requestedA data export job was submitted and processing has started.
export.completedA data export has been generated and is ready for download.
export.downloadedA data export file has been downloaded.
export.failedA data export has failed to generate.

EventDescription
fraud_prevention.triggeredA fraud prevention rule with a Notify action has been triggered.

EventDescription
invoice.createdAn invoice is created (e.g., when a subscription is created or renewed).
invoice.payment_action_requiredAn invoice payment attempt requires further customer action to complete.
invoice.payment_failedAn invoice payment attempt fails.
invoice.payment_succeededAn invoice payment attempt succeeds.
invoice.upcomingThe configured number of days before an invoice is scheduled to be created is reached.
invoice.updatedAn invoice is updated.

EventDescription
member.activatedA member activates their membership under a subscription they were invited to.
member.createdA member is invited to join a subscription.
member.updatedA member's information is updated.
member.deletedA member is removed from a subscription.
member.subscription_updatedThe subscription associated with a member is updated.

EventDescription
newsletter.createdA newsletter subscription is created for a customer.
newsletter.updatedA newsletter subscription is updated for a customer.

EventDescription
order.createdA new e-commerce order is created.
order.payment.succeededAn order payment is successfully processed.
order.payment.failedAn order payment attempt fails.

EventDescription
source.createdA new payment method is added to a customer's account.
source.updatedA customer's payment method is updated.
source.expiredA customer's payment method has expired.
source.expiringA customer's payment method is expiring within 30 days.

EventDescription
plan.createdA new subscription plan is created.
plan.updatedA subscription plan is updated.

EventDescription
product.createdA new product is created.
product.updatedA product is updated.
product.deletedA product is deleted.
product_sku.createdA new product SKU (variant) is created.
product_sku.updatedA product SKU is updated.
product_sku.deletedA product SKU is deleted.

EventDescription
subscription.createdA new subscription is created.
subscription.updatedA subscription is updated or transitions between statuses.
subscription.changedA subscription is changed (plan or billing cycle modification).
subscription.canceledA subscription is canceled.
subscription.expiredA subscription has expired.
subscription.renewedA subscription renews and a new billing period begins.
subscription.trial_will_endA subscription's trial period is ending within three days, or a trial is ended immediately.
subscription.gift_notificationIt's time to notify the gift recipient, according to the gift date set during creation.

EventDescription
vendor.createdA new vendor is created.
vendor.updatedA vendor's profile is updated.
vendor.deletedA vendor is deleted.

Security

For added security, your integration should make sure the events it receives originate from Pelcro's infrastructure. See the list of outgoing IP addresses to whitelist.