Skip to main content

Receive Benecaster webhooks safely — verify the signature and ignore retries you already handled

Free Intermediate

Benecaster retries a delivery that did not get a 2xx — 5 minutes later, then 30 minutes after that — so a receiver that was slow to answer can see the same event twice. Every attempt carries the same X-Benecaster-Delivery ID: de-duplicate on it. Verify the X-Benecaster-Signature header against the raw body, before decoding it.

This receiver runs on another WordPress site, but any language works the same way: compute sha256= + the HMAC-SHA256 of the raw body with the webhook’s secret, and compare it in constant time. Every event and its keys are on the Outbound Webhooks page.

Code

<?php
// A receiver on another WordPress site (any language works the same way).
add_action( 'rest_api_init', function (): void {
    register_rest_route( 'my-crm/v1', '/benecaster', [
        'methods'             => 'POST',
        'permission_callback' => '__return_true',
        'callback'            => function ( WP_REST_Request $request ) {
            $raw      = $request->get_body();
            $expected = 'sha256=' . hash_hmac( 'sha256', $raw, MY_BENECASTER_SECRET );
            if ( ! hash_equals( $expected, (string) $request->get_header( 'x-benecaster-signature' ) ) ) {
                return new WP_REST_Response( null, 401 );
            }

            $delivery = (string) $request->get_header( 'x-benecaster-delivery' );
            if ( get_transient( 'bc_seen_' . md5( $delivery ) ) ) {
                return new WP_REST_Response( null, 200 ); // a retry we already handled
            }
            set_transient( 'bc_seen_' . md5( $delivery ), 1, DAY_IN_SECONDS );

            $event = json_decode( $raw, true );
            if ( 'token.revoked' === $event['event'] && 'admin_revoked' === $event['reason'] ) {
                my_crm_tag( $event['email'], 'removed-by-podcaster' );
            }
            return new WP_REST_Response( null, 200 ); // answer fast; queue heavy work
        },
    ] );
} );

View on GitHub →

Need this built rather than just documented? See our services →