You’re running a service business. A customer books a job on your WordPress site. Then what? Does it sit in Jobber while your phone stays silent? Does your team find out hours later? Does nobody follow up?
Here’s the reality: webhooks bridge that gap. When Jobber detects an event—a new booking, a quote update, a job completion—it can instantly fire a message to your Slack, send you an SMS, trigger an email, or sync data to your CRM. No manual checking. No delays. Just automation that works while you’re focused on the actual work.
We’re going to walk through how to set this up on WordPress, with real code you can copy and adapt. By the end, you’ll have a 5-minute response system that keeps your team aligned and your customers engaged.
What Are Webhooks? (And Why They Matter)
A webhook is basically a reverse API call. Instead of your code asking Jobber “Hey, did anything happen?” on a schedule, Jobber proactively tells you when something does happen.
Think of it like a doorbell. You don’t have to stand at the door checking every second. When someone presses the button, it rings. Boom—you know.
In Jobber’s world, events include:
- Job created — New booking submitted
- Quote updated — Customer accepted/declined a quote
Each event sends a payload (JSON data) to a URL on your site. WordPress listens at that URL, processes the data, and triggers downstream actions—Slack messages, SMS alerts, database logs, CRM syncs. All within seconds.
Why this matters: Service businesses live on speed and follow-up. The faster you respond to a booking or a quote acceptance, the faster you move deals forward. Webhooks eliminate the lag.
Setting Up Your First Jobber Webhook in WordPress
Before you write any code, you need a WordPress endpoint that can receive webhook payloads from Jobber.
The modern WordPress way is to use REST API routes or custom action hooks. We’ll use action hooks because they integrate cleanly with WordPress theme/plugin architecture.
Here’s the basic structure:
// In your theme's functions.php or a custom plugin file
add_action('jobber_webhook_job/create', function($payload) {
// This fires every time a job is created in Jobber
// $payload contains all the booking details
// Log it
error_log('New Jobber job: ' . json_encode($payload));
// Now do something with it (we'll show you more below)
});
The hook name follows the pattern: jobber_webhook_[event_type]. So:
jobber_webhook_job/create— new jobjobber_webhook_quote/update— quote status changed
Now, the real question: Where does this hook fire from?
In a typical setup, you’d have a REST endpoint or a simple PHP script that:
- Receives the Jobber webhook POST request
- Verifies it’s legitimate (we’ll cover security below)
- Extracts the payload
- Fires the appropriate WordPress action hook
If you’re using a plugin (like Jobber’s native WordPress integration), this is already set up. If you’re building custom, you’d add something like this to handle incoming webhooks:
add_action('rest_api_init', function() {
register_rest_route('jobber/v1', '/webhook', [
'methods' => 'POST',
'callback' => 'jobber_handle_webhook',
'permission_callback' => '__return_true', // We'll verify the signature instead
]);
});
function jobber_handle_webhook($request) {
$payload = $request->get_json_params();
$signature = $request->get_header('X-Jobber-Signature');
// Verify the webhook came from Jobber (see Security section below)
if (!jobber_verify_signature($signature, $payload)) {
return new WP_Error('invalid_signature', 'Webhook signature invalid', ['status' => 401]);
}
// Extract event type
$event = $payload['event'] ?? 'unknown';
// Fire the appropriate hook
do_action("jobber_webhook_$event", $payload);
return ['success' => true];
}
Now you have a webhook endpoint. Jobber sends data. WordPress receives it and triggers actions. Let’s do something useful with it.
Real-World Automation: Slack Notifications on New Bookings
This is the most common first automation. A customer books a job. Your Slack channel lights up instantly.
add_action('jobber_webhook_job/create', function($payload) {
// Extract client info
$client = $payload['data']['client'] ?? [];
$firstName = $client['firstName'] ?? 'Unknown';
$lastName = $client['lastName'] ?? '';
$email = $client['email'] ?? '';
$phone = $client['phone'] ?? '';
// Extract job details
$jobTitle = $payload['data']['title'] ?? 'Service Request';
$service = $payload['data']['service'] ?? 'General';
$notes = $payload['data']['notes'] ?? '';
$scheduledDate = $payload['data']['scheduledDate'] ?? 'TBD';
// Build the Slack message
$slackMessage = [
'text' => "🆕 NEW BOOKING: $firstName $lastName",
'blocks' => [
[
'type' => 'section',
'text' => [
'type' => 'mrkdwn',
'text' => "*New Booking from $firstName $lastName*n$email | $phone"
]
],
[
'type' => 'section',
'fields' => [
['type' => 'mrkdwn', 'text' => "*Service:*n$service"],
['type' => 'mrkdwn', 'text' => "*Scheduled:*n$scheduledDate"],
['type' => 'mrkdwn', 'text' => "*Title:*n$jobTitle"],
['type' => 'mrkdwn', 'text' => "*Notes:*n$notes"]
]
],
[
'type' => 'section',
'text' => [
'type' => 'mrkdwn',
'text' => "<https://app.getjobber.com/jobs|View in Jobber>"
]
]
]
];
// Send to Slack
$webhookUrl = get_option('jobber_slack_webhook_url');
if ($webhookUrl) {
wp_remote_post($webhookUrl, [
'body' => json_encode($slackMessage),
'headers' => ['Content-Type' => 'application/json'],
]);
}
});
To make this work, you need to:
- Create a Slack app and generate a webhook URL
- Store that URL in WordPress options:
update_option('jobber_slack_webhook_url', 'https://hooks.slack.com/...') - The rest happens automatically
That’s it. Every new booking triggers a beautifully formatted Slack message to your team. They see client name, contact info, service type, and a direct link to Jobber.
SMS Alerts for Urgent Bookings
What if you want immediate SMS alerts, but only for urgent jobs? Use Twilio to filter and send texts.
add_action('jobber_webhook_job/create', function($payload) {
$notes = $payload['data']['notes'] ?? '';
$client = $payload['data']['client'] ?? [];
$phone = $client['phone'] ?? '';
// Only SMS for emergencies
$isUrgent = (
stripos($notes, 'emergency') !== false ||
stripos($notes, 'urgent') !== false ||
stripos($notes, 'asap') !== false
);
if (!$isUrgent) {
return; // Not urgent, skip SMS
}
// Twilio credentials (store in wp_options or .env)
$sid = get_option('twilio_account_sid');
$token = get_option('twilio_auth_token');
$fromNumber = get_option('twilio_from_number');
$toNumber = get_option('jobber_alert_phone'); // Your phone
// Send SMS via Twilio
wp_remote_post("https://api.twilio.com/2010-04-01/Accounts/$sid/Messages.json", [
'headers' => [
'Authorization' => 'Basic ' . base64_encode("$sid:$token"),
],
'body' => [
'From' => $fromNumber,
'To' => $toNumber,
'Body' => "URGENT: New booking from {$client['firstName']} - {$notes}"
]
]);
});
This is powerful for plumbing emergencies, HVAC rush jobs, or any service where “ASAP” means “wake me up now.”
Email Notifications: Simple but Effective
WordPress’s native wp_mail() function works great for webhook-triggered emails. No fancy API required.
add_action('jobber_webhook_job/create', function($payload) {
$client = $payload['data']['client'] ?? [];
$job = $payload['data'];
$to = get_option('jobber_notification_email');
$subject = "New Booking: {$client['firstName']} {$client['lastName']}";
$body = sprintf(
"New job created:nn" .
"Client: %s %sn" .
"Email: %sn" .
"Phone: %sn" .
"Service: %sn" .
"Notes: %snn" .
"View in Jobber: https://app.getjobber.com/jobs/%s",
$client['firstName'],
$client['lastName'],
$client['email'],
$client['phone'],
$job['service'] ?? 'General',
$job['notes'] ?? '(none)',
$job['id'] ?? ''
);
wp_mail($to, $subject, $body);
});
Email is slower than Slack (a few seconds vs. instant), but it creates a paper trail and works even if your team isn’t monitoring Slack.
Quote Follow-Up Sequences
When Jobber sends a quote, you can trigger an automated email sequence to follow up if the customer hasn’t responded in X days.
add_action('jobber_webhook_quote/update', function($payload) {
$quote = $payload['data'];
$clientId = $quote['clientId'] ?? null;
$quoteId = $quote['id'] ?? null;
if (!$clientId || !$quoteId) {
return;
}
// Store quote data in WordPress custom post type for tracking
wp_insert_post([
'post_type' => 'jobber_quote',
'post_title' => "Quote #{$quoteId}",
'post_content' => json_encode($quote),
'meta_input' => [
'jobber_quote_id' => $quoteId,
'jobber_client_id' => $clientId,
'quote_sent_date' => current_time('mysql'),
'follow_up_scheduled' => 0,
]
]);
// Schedule a follow-up check in 2 days
wp_schedule_single_event(
time() + (2 * DAY_IN_SECONDS),
'jobber_quote_follow_up_check',
[$quoteId]
);
});
add_action('jobber_quote_follow_up_check', function($quoteId) {
// Get the quote from Jobber API
// If status is still 'draft' (not accepted), send follow-up email
// (This requires a Jobber API call—beyond scope here, but the pattern works)
});
This keeps your pipeline warm without manual effort.
CRM Sync: Push Jobber Data to HubSpot or Mailchimp
When a new job is created in Jobber, automatically sync the client data to your CRM so your marketing team can nurture them.
add_action('jobber_webhook_job/create', function($payload) {
$client = $payload['data'];
// Example: Add to HubSpot
$hubspotApiKey = get_option('hubspot_api_key');
$hubspotUrl = 'https://api.hubapi.com/crm/v3/objects/contacts';
wp_remote_post($hubspotUrl, [
'headers' => [
'Authorization' => "Bearer $hubspotApiKey",
'Content-Type' => 'application/json',
],
'body' => json_encode([
'properties' => [
'firstname' => $client['firstName'] ?? '',
'lastname' => $client['lastName'] ?? '',
'email' => $client['email'] ?? '',
'phone' => $client['phone'] ?? '',
'hs_lead_status' => 'new',
]
])
]);
});
Now every new Jobber client is automatically in HubSpot. Your sales team gets visibility. Zero manual data entry.
Custom Logging: Build Your Own Jobber Dashboard
Create a custom WordPress post type to log all incoming webhooks. Then build a dashboard widget to see your booking pipeline in real-time.
// Register custom post type
register_post_type('jobber_log', [
'label' => 'Jobber Logs',
'public' => false,
'show_in_rest' => true,
'supports' => ['title', 'editor', 'custom-fields'],
]);
// Log every webhook
add_action('jobber_webhook_job/create', function($payload) {
wp_insert_post([
'post_type' => 'jobber_log',
'post_title' => 'Job Created: ' . ($payload['data']['title'] ?? 'Untitled'),
'post_content' => json_encode($payload, JSON_PRETTY_PRINT),
'meta_input' => [
'event_type' => 'job/create',
'client_id' => $payload['data']['clientId'] ?? '',
'job_id' => $payload['data']['id'] ?? '',
'timestamp' => time(),
]
]);
});
Then query this post type in your dashboard to show today’s bookings, weekly totals, trends, etc.
Webhook Security: Verify the Signature
This is critical. Anyone could POST to your webhook endpoint and pretend to be Jobber. You must validate the signature.
Jobber signs each webhook with an HMAC-SHA256 hash. Here’s how to verify it:
function jobber_verify_signature($signature, $payload) {
$secret = get_option('jobber_webhook_secret');
// Jobber includes the raw body in the signature calculation
// Make sure you're hashing the raw JSON, not the parsed PHP array
$body = file_get_contents('php://input');
$expectedSignature = 'sha256=' . hash_hmac('sha256', $body, $secret);
// Use hash_equals to prevent timing attacks
return hash_equals($expectedSignature, $signature);
}
add_action('rest_api_init', function() {
register_rest_route('jobber/v1', '/webhook', [
'methods' => 'POST',
'callback' => 'jobber_handle_webhook',
'permission_callback' => '__return_true', // We verify signature instead
]);
});
function jobber_handle_webhook($request) {
$signature = $request->get_header('X-Jobber-Signature');
// This must be the raw body, not the parsed payload
$body = file_get_contents('php://input');
if (!jobber_verify_signature($signature, $body)) {
return new WP_Error('invalid_signature', 'Webhook signature invalid', ['status' => 401]);
}
// Now process the payload
$payload = json_decode($body, true);
$event = $payload['event'] ?? 'unknown';
do_action("jobber_webhook_$event", $payload);
return ['success' => true];
}
Store your webhook secret in WordPress options (or better, in your .env file and loaded via wp-config.php). Jobber provides this when you create the webhook in their dashboard.
Error Handling and Retry Logic
What if your Slack API is down? What if the email fails? You need retry logic.
function jobber_send_slack($message) {
$webhookUrl = get_option('jobber_slack_webhook_url');
$response = wp_remote_post($webhookUrl, [
'body' => json_encode($message),
'headers' => ['Content-Type' => 'application/json'],
]);
if (is_wp_error($response)) {
// Log the error and schedule a retry
error_log('Slack webhook failed: ' . $response->get_error_message());
// Retry in 5 minutes
wp_schedule_single_event(
time() + (5 * MINUTE_IN_SECONDS),
'jobber_slack_retry',
[$message]
);
return false;
}
$statusCode = wp_remote_retrieve_response_code($response);
if ($statusCode >= 400) {
error_log("Slack returned status $statusCode");
return false;
}
return true;
}
add_action('jobber_slack_retry', function($message) {
jobber_send_slack($message);
});
For critical integrations, consider storing failed webhook payloads in the database and retrying them via a scheduled action that runs every 15 minutes.
Real-World Automation Workflows
Here are four complete, battle-tested workflows:
1. Five-Minute Response System
New booking → Slack notification to team channel + SMS to owner + email to service coordinator. All within 5 seconds.
add_action('jobber_webhook_job/create', function($payload) {
// Slack to team
jobber_send_slack_notification($payload);
// SMS to owner if urgent
if (is_urgent($payload)) {
jobber_send_sms_alert($payload);
}
// Email to coordinator
jobber_send_coordinator_email($payload);
// Log it
jobber_log_event('job/create', $payload);
});
This ensures no booking slips through the cracks. Your team is always in the loop.
2. Quote Acceptance Workflow
Quote sent → 2-day follow-up if not viewed → 5-day follow-up if not accepted → final reminder at day 7.
add_action('jobber_webhook_quote/update', function($payload) {
$quoteId = $payload['data']['id'];
// Schedule follow-ups
wp_schedule_single_event(time() + (2 * DAY_IN_SECONDS), 'jobber_quote_followup_1', [$quoteId]);
wp_schedule_single_event(time() + (5 * DAY_IN_SECONDS), 'jobber_quote_followup_2', [$quoteId]);
wp_schedule_single_event(time() + (7 * DAY_IN_SECONDS), 'jobber_quote_followup_3', [$quoteId]);
});
add_action('jobber_quote_followup_1', function($quoteId) {
// Check if quote was accepted
// If not, send gentle follow-up email
});
This turns “crickets” into “conversions.” Quotes don’t just sit—they get attention.
3. Quote Response Tracking
Quote status updated → track if it was accepted or declined.
add_action('jobber_webhook_quote/update', function($payload) {
$quote = $payload['data'];
$status = $quote['status'] ?? 'unknown';
// Log quote status changes
error_log("Quote status changed to: {$status}");
});
Track quote acceptance rates to optimize your pricing and follow-up strategy.
4. Weekly Booking Report
Every Monday morning, send a digest of last week’s bookings to management.
add_action('wp_scheduled_event', 'jobber_weekly_report', function() {
$startOfWeek = strtotime('last Monday midnight');
$endOfWeek = strtotime('today midnight');
$bookings = get_posts([
'post_type' => 'jobber_log',
'meta_query' => [
['key' => 'event_type', 'value' => 'job/create'],
['key' => 'timestamp', 'compare' => '>=', 'value' => $startOfWeek],
['key' => 'timestamp', 'compare' => '<=', 'value' => $endOfWeek],
],
'numberposts' => -1,
]);
$total = count($bookings);
$totalValue = array_sum(array_map(function($b) {
return (float) get_post_meta($b->ID, 'job_value', true);
}, $bookings));
wp_mail(
'[email protected]',
"Weekly Booking Report: $total jobs, $$totalValue revenue",
// format as HTML table
);
});
// Schedule to run every Monday at 8am
if (!wp_next_scheduled('jobber_weekly_report')) {
wp_schedule_event(strtotime('next Monday 8am'), 'weekly', 'jobber_weekly_report');
}
Management always knows the score. No Excel spreadsheet needed.
Testing Webhooks Locally During Development
During development, you can’t receive webhooks from Jobber’s servers (they can’t reach your localhost). Use ngrok to tunnel your local WordPress to a public URL.
- Install ngrok:
brew install ngrok(macOS) or download from ngrok.com - Run:
ngrok http 8000(if your local site is on port 8000) - ngrok gives you a public URL:
https://abc123.ngrok.io - In Jobber’s webhook settings, use:
https://abc123.ngrok.io/wp-json/jobber/v1/webhook - Test a booking—the webhook posts to Jobber, Jobber sends it to ngrok, ngrok tunnels it to your local WordPress
Watch your local logs in real-time as webhooks arrive:
tail -f /path/to/wordpress/wp-content/debug.log
Boom. You see every webhook, every error, every action firing. Much faster than deploying to staging.
Bringing It All Together
Webhooks are the connective tissue between Jobber and everything else you use—Slack, email, SMS, CRM, accounting software. They eliminate manual data entry, speed up response times, and keep your team in sync.
Start simple. Get Slack notifications working. Test locally with ngrok. Once that’s solid, add SMS for emergencies. Then CRM sync. Then reporting dashboards. Each piece builds on the last.
The businesses that win aren’t the ones with the fanciest software. They’re the ones where information moves fast and decisions happen instantly. Webhooks do that for you.
Set this up this week. Your team will wonder how you ever lived without it.
Need help wiring this together? Check out the complete Jobber WordPress integration guide for the full picture. Or explore quote forms and line items if your clients are complex on estimates.
Ready to connect Jobber to your WordPress site? Native forms, real-time API, no Zapier needed. Get Jobber Integration Pro – $99/year