NAME
Business::Stripe::Webhook - A Perl module for handling webhooks sent by Stripe
VERSION
Version 1.12
SYNOPSIS
use Stripe::Webhook;
my $payload;
read(STDIN, $payload, $ENV{'CONTENT_LENGTH'});
my $webhook = Stripe::Webhook->new(
signing_secret => 'whsec_...',
api_secret => 'sk_test_...',
payload => $payload,
invoice-paid => \&update_invoice,
checkout-session-completed => \&update_session,
);
die $webhook->error unless $webhook->success;
my $result = $webhook->process();
if ($webhook->success()) {
$webhook->reply(status => 'OK');
} else {
$webhook->reply(error => $webhook->error());
}
sub update_invoice {
# Process paid invoice
...
}
sub update_session {
# Process checkout
...
}
DESCRIPTION
Business::Stripe::Webhook is a Perl module that provides an interface for handling webhooks sent by Stripe. It provides a simple way to verify the signature of the webhook, and allows the user to define a number of methods for processing specific types of webhooks.
This module is designed to run on a webserver as that is where Stripe webhooks would typically be sent. It reads the payload sent from Stripe from STDIN
because Stripe sends an HTTP POST
request. Ensure that no other module is reading from STDIN
or Business::Stripe::Webhook will not get the correct input.
Workflow
The typical workflow for Business::Stripe::Webhook is to initally create an instance of the module and to define one or more Stripe events to listen for. This is done by providing references to your subroutines as part of the new
method. Note that the webhook events you want to listen for need to be enabled in the Stripe Dashboard.
my $webhook = Stripe::Webhook->new(
invoice-paid => \&sub_to_handle_paid_invoice,
);
The Stripe event names have a fullstop replaced with a minus sign. So, invoice.paid
becomes invoice-paid
.
Next is to process the webhook from Stripe.
my $result = $webhook->process();
This will call the subroutines that were defined when the module was created and pass them the event object from Stripe.
Finally, a reply is sent back to Stripe.
print reply(status => 'OK');
This produces a fully formed HTTP Response complete with headers as required by Stripe.
Reply time
Stripe requires a timely reply to webhook calls. Therefore, if you need to carry out any lengthy processing after the webhook has been sent, this should be done after calling the reply
method and flushing STDOUT
use Stripe::Webhook;
my $webhook = Stripe::Webhook->new(
signing_secret => 'whsec_...',
payload => $payload,
invoice-paid => \&update_invoice,
);
$webhook->process();
# Send reply for unhandled webhooks
$webhook->reply();
sub invoice-paid {
# Send reply quickly and flush buffer
print $webhook->reply();
select()->flush();
# Process paid invoice which will take time then do not return
...
exit;
}
Errors and Warnings
By default, any errors or warnings are sent to STDERR
. These can be altered to instead go to your own subroutine to handle errors and/or warnings by defining these when create the object.
my $webhook = Stripe::Webhook->new(
invoice-paid => \&sub_to_handle_paid_invoice,
error => \&my_error_handler,
warning => \&my_warning_handler,
);
Additionally, warnings can be turned off by setting the warning
parameter to nowarn
. Errors cannot be turned off.
METHODS
new
Creates a new Stripe::Webhook object.
my $webhook = Stripe::Webhook->new(
signing_secret => 'whsec_...',
payload => $payload,
);
This method takes one or more parameters:
signing_secret: The webhook signing secret provided by Stripe. If omitted, the Stripe Signature will not be checked.
payload: A JSON string. Required (see below). The JSON object from Stripe.
api_secret: The Stripe secret API Key - see https://stripe.com/docs/keys. Optional but will be required if the
get_subscription
method is needed.stripe-event: One or more callbacks to the subroutines to handle the webhooks events sent by Stripe. See https://stripe.com/docs/api/events/list.
To listen for an event, change the fullstop in the Stripe event name to a minus sign and use that as the parameter. The events you define should match the events you ask Stripe to send. Any events Stripe sends that do not have a callback defined will be ignored (unless
all-webhooks
is defined).Stripe event
invoice.paid
becomesinvoice-paid
Stripe eventinvoice.payment_failed
becomesinvoice-payment_failed
all-webhooks: A callback subroutine which will be called for every event received from Stripe even if a callback subroutine for that event has not been defined.
error: A callback subroutine to handle errors. If not defined, errors are sent to
STDERR
.warning: A callback subroutine to handle warnings. If not defined, warnings are sent to
STDERR
. If set tonowarn
, warnings are ignored.
Previous versions on Business::Stripe::Webhook allowed the payload parameter to be omitted. In this case, the module would read STDIN
to obtain the JSON string. This continues to work for backward compatability only but will be removed from furture versions.
success
Returns true if the last operation was successful, or false otherwise.
if ($webhook->success()) {
...
}
error
Returns the error message from the last operation, or an empty string if there was no error.
my $error = $webhook->error();
process
This method processes the webhook sent from Stripe. It checks the Stripe Signature if a signing_secret
parameter has been included and calls the defined subroutine to handle the Stripe event. Each subroutine is passed a JSON decoded Event Object from Stripe.
my $result = $webhook->process();
This method takes no parameters.
Normally, the return value can be ignored. Returns undef
if there was an error or warning.
check_signature
Checks the signature of the webhook to verify that it was sent by Stripe.
my $sig_ok = $webhook->check_signature();
This method takes no parameters.
Normally, this method does not need to be called. It is called by the process
method if a signing_secret
parameter was included when the object was created.
reply
Sends a reply to Stripe.
print reply(status => 'OK');
It takes one or more optional parameters.
Parameters passed to this method are then passed through to Stripe. These are available in the Stripe Dashboard and are especially useful for troubleshooting during development.
The following parameters are always passed to Stripe:
status:
noaction
if the event did not have a handler,success
if the event was handled orfailed
if it wasn'tsent_to: An array containing the names of the callback subroutines that handled the event.
sent_to_all:
true
orfalse
to indicate if theall-webhooks
parameter was settimestamp: The server time at which the webhook was handled
get_subscription
Retrieves a subscription object from Stripe. This is required to retrieve information such as the end of the current subscription period or whether the subscription is set to cancel after the current period.
my $response = $webhook->get_subscription($subscription_id, $secret_key);
This method takes two parameters:
$subscription_id: The ID of the subscription to retrieve. Required.
$secret_key: The secret API key to use to retrieve the subscription. Optional.
This is usually supplied when the object is created but can be supplied when calling this method. If the API Key has alreay been supplied, this paramter will override the previous key.
An HTTP::Tiny response is returned representing the Subscription Object from Stripe - see https://perldoc.perl.org/HTTP::Tiny#request
Note: times sent from Stripe are in seconds since the epoch. If adding them to a database which would be a typical scenario, use the SQL FROM_UNIXTIME
function:
$dbh->do("UPDATE table SET currentPeriodEnd = FROM_UNIXTIME( ? ) WHERE idSubscription = ?", undef, $response->{'current_period_end'}, $response->{'subscription'});
EXAMPLES
Here's an example of how to use the module to handle a webhook:
use Business::Stripe::Webhook;
my $payload;
read(STDIN, $payload, $ENV{'CONTENT_LENGTH'});
my $webhook = Business::Stripe::Webhook->new(
signing_secret => 'whsec_...',
payload => $payload,
invoice-paid => \&pay_invoice,
);
$webhook->process();
print $webhook->reply;
sub pay_invoice {
my $event = $_[0];
my $subscription = $event->{'data'}->{'object'}->{'subscription'};
}
Here's an example of how to use the module to retrieve a subscription object:
use Business::Stripe::Webhook;
use JSON::PP;
my $webhook = Business::Stripe::Webhook->new(
api_secret => 'sk_...',
);
my $response = $webhook->get_subscription('sub_...');
if ($response->{'success'}) {
my $subscription = decode_json($response->{'content'});
...
} else {
my $error = $response->{'content'};
...
}
SEE ALSO
Business::Stripe::Subscription
AUTHOR
Ian Boddison <ian at boddison.com>
BUGS
Please report any bugs or feature requests to bug-business-stripe-webhook at rt.cpan.org
, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=bug-business-stripe-webhook. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Business::Stripe::Webhook
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
https://rt.cpan.org/NoAuth/Bugs.html?Dist=Business-Stripe-Webhook
Search CPAN
ACKNOWLEDGEMENTS
Thanks to the help and support provided by members of Perl Monks https://perlmonks.org/.
COPYRIGHT AND LICENSE
This software is copyright (c) 2023 by Ian Boddison.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.