-
-
Notifications
You must be signed in to change notification settings - Fork 0
Webhooks
Manage webhooks for real-time event notifications in Teamleader Focus.
The Webhooks resource allows you to register and manage webhooks for real-time notifications when events occur in Teamleader. Webhooks enable your application to respond immediately to changes like new invoices, updated deals, or created contacts without constantly polling the API.
- Endpoint
- Capabilities
- Available Methods
- Helper Methods
- Event Types
- Response Structure
- Webhook Payload
- Usage Examples
- Common Use Cases
- Best Practices
- Error Handling
- Webhook Verification
- Related Resources
webhooks
- Pagination: β Not Supported
- Filtering: β Not Supported
- Sorting: β Not Supported
- Sideloading: β Not Supported
-
Creation: β
Supported (via
register()) - Update: β Not Supported
-
Deletion: β
Supported (via
unregister())
Get all registered webhooks, ordered by URL.
Example:
use McoreServices\TeamleaderSDK\Facades\Teamleader;
$webhooks = Teamleader::webhooks()->list();
foreach ($webhooks['data'] as $webhook) {
echo "URL: {$webhook['url']}\n";
echo "Types: " . implode(', ', $webhook['types']) . "\n";
}Register a new webhook for specific event types.
Parameters:
-
url(string, required): Your webhook URL β must use HTTPS -
types(array, required): Array of event type strings to subscribe to
Example:
Teamleader::webhooks()->register(
'https://example.com/webhooks/teamleader',
[
'invoice.booked',
'invoice.sent',
'invoice.paymentRegistered',
]
);Remove specific event types from a registered webhook. Both url and types are required.
Parameters:
-
url(string, required): The webhook URL to unregister from -
types(array, required): Array of event type strings to remove
Example:
// Remove specific event types from a webhook
Teamleader::webhooks()->unregister(
'https://example.com/webhooks/teamleader',
['invoice.booked', 'invoice.sent']
);
// Remove all event types (effectively deletes the webhook)
$webhooks = Teamleader::webhooks()->list();
$url = 'https://example.com/webhooks/teamleader';
foreach ($webhooks['data'] as $webhook) {
if ($webhook['url'] === $url) {
Teamleader::webhooks()->unregister($url, $webhook['types']);
break;
}
}Returns the full list of valid event type strings.
$allTypes = Teamleader::webhooks()->getAvailableEventTypes();Filter event types by their category prefix.
$receiptTypes = Teamleader::webhooks()->getEventTypesByCategory('receipt');
// ['receipt.added', 'receipt.approved', 'receipt.deleted', ...]// invoice + incomingInvoice events combined
$invoiceTypes = Teamleader::webhooks()->getInvoiceEventTypes();
// creditNote + incomingCreditNote events combined
$creditNoteTypes = Teamleader::webhooks()->getCreditNoteEventTypes();
// deal events
$dealTypes = Teamleader::webhooks()->getDealEventTypes();
// contact events
$contactTypes = Teamleader::webhooks()->getContactEventTypes();
// company events
$companyTypes = Teamleader::webhooks()->getCompanyEventTypes();
// project + nextgenProject events combined
$projectTypes = Teamleader::webhooks()->getProjectEventTypes();
// task + nextgenTask events combined
$taskTypes = Teamleader::webhooks()->getTaskEventTypes();
// ticket + ticketMessage events combined
$ticketTypes = Teamleader::webhooks()->getTicketEventTypes();
// timeTracking events
$timeTrackingTypes = Teamleader::webhooks()->getTimeTrackingEventTypes();account.deactivatedaccount.deleted
call.addedcall.completedcall.deletedcall.updated
company.addedcompany.deletedcompany.updated
contact.addedcontact.deletedcontact.linkedToCompanycontact.unlinkedFromCompanycontact.updatedLinkToCompanycontact.updated
creditNote.bookedcreditNote.deletedcreditNote.peppolSubmissionFailedcreditNote.peppolSubmissionSucceededcreditNote.sentcreditNote.updated
deal.createddeal.deleteddeal.lostdeal.moveddeal.updateddeal.won
incomingCreditNote.addedincomingCreditNote.approvedincomingCreditNote.bookkeepingSubmissionFailedincomingCreditNote.bookkeepingSubmissionSucceededincomingCreditNote.deletedincomingCreditNote.refusedincomingCreditNote.updated
incomingInvoice.addedincomingInvoice.approvedincomingInvoice.bookkeepingSubmissionFailedincomingInvoice.bookkeepingSubmissionSucceededincomingInvoice.deletedincomingInvoice.refusedincomingInvoice.updated
invoice.bookedinvoice.deletedinvoice.draftedinvoice.paymentRegisteredinvoice.paymentRemovedinvoice.peppolSubmissionFailedinvoice.peppolSubmissionSucceededinvoice.sentinvoice.updated
meeting.completedmeeting.createdmeeting.deletedmeeting.updated
milestone.createdmilestone.updated
nextgenProject.closednextgenProject.creatednextgenProject.deletednextgenProject.updated
nextgenTask.completednextgenTask.creatednextgenTask.deletednextgenTask.updated
product.addedproduct.deletedproduct.updated
project.createdproject.deletedproject.updated
receipt.addedreceipt.approvedreceipt.bookkeepingSubmissionFailedreceipt.bookkeepingSubmissionSucceededreceipt.deletedreceipt.refusedreceipt.updated
subscription.addedsubscription.deactivatedsubscription.deletedsubscription.updated
task.completedtask.createdtask.deletedtask.updated
ticket.closedticket.createdticket.deletedticket.reopenedticket.updatedticketMessage.added
timeTracking.addedtimeTracking.deletedtimeTracking.updated
user.deactivated
[
'data' => [
[
'url' => 'https://example.com/webhooks/teamleader',
'types' => [
'invoice.booked',
'invoice.sent',
'invoice.paymentRegistered',
],
],
],
]Both return an empty array on success (HTTP 204 No Content).
When an event fires, Teamleader POSTs a JSON payload to your URL. The actual payload structure is:
{
"type": "company.updated",
"subject": {
"type": "company",
"id": "entity-uuid"
},
"account": {
"type": "account",
"id": "account-uuid"
}
}Note: The entity ID is at
subject.id, notdata.id. Thesubject.typefield matches the entity category (e.g.company,contact,invoice). There is nometa.timestampfield in the payload β use your own server timestamp if you need to track delivery time.
$types = Teamleader::webhooks()->getInvoiceEventTypes();
Teamleader::webhooks()->register('https://myapp.com/webhooks/teamleader', $types);Teamleader::webhooks()->register('https://myapp.com/webhooks/teamleader', [
'invoice.peppolSubmissionSucceeded',
'invoice.peppolSubmissionFailed',
'creditNote.peppolSubmissionSucceeded',
'creditNote.peppolSubmissionFailed',
]);Teamleader::webhooks()->register(
'https://myapp.com/webhooks/teamleader',
[
'invoice.booked',
'deal.won',
'deal.lost',
'ticket.created',
'contact.added',
]
);$webhooks = Teamleader::webhooks()->list();
foreach ($webhooks['data'] as $webhook) {
echo "Webhook URL: {$webhook['url']}\n";
echo "Listening to " . count($webhook['types']) . " event types\n\n";
}// routes/web.php or a controller
Route::post('/webhooks/teamleader', function (Request $request) {
$payload = $request->json()->all();
$eventType = $payload['type'];
$id = $payload['subject']['id']; // Note: subject.id, not data.id
Log::info('Webhook received', ['type' => $eventType, 'id' => $id]);
switch ($eventType) {
case 'invoice.booked':
handleInvoiceBooked($id);
break;
case 'invoice.peppolSubmissionFailed':
handlePeppolFailure($id);
break;
case 'deal.won':
handleDealWon($id);
break;
case 'ticket.created':
handleTicketCreated($id);
break;
}
return response()->json(['status' => 'received'], 200);
});$eventTypes = config('teamleader.webhook_events', [
'invoice.booked',
'invoice.paymentRegistered',
]);
$webhookUrl = config('app.url') . '/webhooks/teamleader';
try {
Teamleader::webhooks()->register($webhookUrl, $eventTypes);
Log::info('Webhook registered successfully');
} catch (Exception $e) {
Log::error('Webhook registration failed: ' . $e->getMessage());
}class InvoiceWebhookHandler
{
public function handle(array $payload): void
{
$eventType = $payload['type'];
$invoiceId = $payload['subject']['id']; // Note: subject.id, not data.id
switch ($eventType) {
case 'invoice.booked':
$this->handleInvoiceBooked($invoiceId);
break;
case 'invoice.sent':
$this->handleInvoiceSent($invoiceId);
break;
case 'invoice.paymentRegistered':
$this->handlePaymentReceived($invoiceId);
break;
case 'invoice.peppolSubmissionFailed':
$this->handlePeppolFailure($invoiceId);
break;
}
}
private function handleInvoiceBooked(string $invoiceId): void
{
$invoice = Teamleader::invoices()->info($invoiceId);
DB::table('invoices')->insert([
'teamleader_id' => $invoiceId,
'invoice_number' => $invoice['data']['invoice_number'],
'created_at' => now(),
]);
Notification::send(
User::admins()->get(),
new InvoiceBookedNotification($invoice['data'])
);
}
private function handlePeppolFailure(string $invoiceId): void
{
$invoice = Teamleader::invoices()->info($invoiceId);
Log::error('Peppol submission failed', [
'invoice_id' => $invoiceId,
'invoice_number' => $invoice['data']['invoice_number'],
'peppol_status' => $invoice['data']['peppol_status'] ?? null,
]);
Notification::send(
User::billingTeam()->get(),
new PeppolFailureNotification($invoice['data'])
);
}
}class DealWebhookHandler
{
public function handle(array $payload): void
{
$eventType = $payload['type'];
$dealId = $payload['subject']['id']; // Note: subject.id, not data.id
if ($eventType === 'deal.won') {
$this->handleDealWon($dealId);
} elseif ($eventType === 'deal.lost') {
$this->handleDealLost($dealId);
}
}
private function handleDealWon(string $dealId): void
{
$deal = Teamleader::deals()->info($dealId);
Teamleader::projects()->create([
'title' => 'Project: ' . $deal['data']['title'],
'customer' => $deal['data']['lead']['customer'],
]);
$this->notifySalesTeam($deal['data']);
}
}- Teamleader will retry failed webhooks
- Always return HTTP 200 to acknowledge receipt
- Process webhooks asynchronously using Laravel queues to avoid timeouts
Webhook URLs must use HTTPS.
Events may not always arrive in chronological order. If ordering matters, record your own server timestamp on receipt.
The API has no update endpoint. To add event types, unregister the current types and re-register the combined set:
$url = 'https://example.com/webhooks/teamleader';
// Get current types
$webhooks = Teamleader::webhooks()->list();
$currentTypes = [];
foreach ($webhooks['data'] as $webhook) {
if ($webhook['url'] === $url) {
$currentTypes = $webhook['types'];
break;
}
}
// Merge and re-register
$newTypes = array_unique(array_merge($currentTypes, ['receipt.added', 'receipt.updated']));
Teamleader::webhooks()->unregister($url, $currentTypes);
Teamleader::webhooks()->register($url, $newTypes);use McoreServices\TeamleaderSDK\Exceptions\TeamleaderException;
try {
Teamleader::webhooks()->register(
'https://myapp.com/webhooks/teamleader',
['invoice.booked']
);
} catch (InvalidArgumentException $e) {
// Invalid URL, non-HTTPS, or unknown event type
Log::error('Webhook validation failed: ' . $e->getMessage());
} catch (TeamleaderException $e) {
if ($e->getCode() === 422) {
Log::error('Webhook registration rejected by API');
}
}
try {
Teamleader::webhooks()->unregister(
'https://myapp.com/webhooks/teamleader',
['invoice.booked']
);
} catch (TeamleaderException $e) {
if ($e->getCode() === 404) {
Log::warning('Webhook was not registered');
}
}- HTTPS Only β Webhook URLs must use HTTPS (enforced by the SDK)
- IP Whitelist β Consider whitelisting Teamleader's IP ranges at your firewall
- Rate Limiting β Protect your endpoint from abuse with throttling middleware
- Idempotency β The same event may be delivered more than once; use the entity ID to deduplicate
class TeamleaderWebhookController extends Controller
{
public function handle(Request $request): JsonResponse
{
// Verify content type
if ($request->header('Content-Type') !== 'application/json') {
return response()->json(['error' => 'Invalid content type'], 400);
}
$payload = $request->json()->all();
// Dispatch to a queued job to avoid timeout
ProcessTeamleaderWebhook::dispatch($payload);
return response()->json(['status' => 'received'], 200);
}
}- Invoices β Invoice events
- Credit Notes β Credit note events, including Peppol
- Deals β Deal events
- Tickets β Ticket events
- Contacts β Contact events
- Companies β Company events
- Receipts β Receipt events
- Usage Guide β General SDK usage
Last Updated: August 2026 β’ SDK Version: 2.2.2 β’ Made with β€οΈ by MCore Services
- Departments
- Users
- Teams
- Custom Fields
- Work Types
- Document Templates
- Currencies
- Notes
- Email Tracking
- Closing Days
- Day Off Types
- Days Off
- User Schedules
- Invoices
- Credit Notes
- Subscriptions
- Payment Methods
- Payment Terms
- Tax Rates
- Withholding Tax Rates
- Commercial Discounts
Next Gen Projects
Legacy Projects