Webhooks allow you to receive real-time notifications when events occur in your GetBill account. Instead of repeatedly polling the API for changes, webhooks push updates directly to your application, making your integration more efficient and responsive.
GetBill signs all webhook requests to ensure they’re authentic and haven’t been tampered with. You should always verify the signature before processing webhook events to protect your application from malicious requests.
Extract the signature from the X-GetBill-Signature header
Get the raw request body (before JSON parsing)
Compute HMAC-SHA256 hash of the raw body using your secret key
Compare using a timing-safe comparison function (prevents timing attacks)
Reject the request if signatures don’t match
Security Critical: Always use a constant-time comparison function (like crypto.timingSafeEqual() in Node.js, hmac.compare_digest() in Python, or hash_equals() in PHP) to prevent timing attacks. Never use simple string equality (== or ===).
debt.status_changed - Debt status was updated. When the debt becomes paid because of a payment transaction, the payload also includes transaction context.
Followup Events
followup.created - A new followup was created
followup.updated - A followup was modified
followup.completed - A followup was marked as completed
followup.failed - A followup attempt failed
Payment Events
payment.received - A payment was recorded
payment.failed - A payment attempt failed
payment_plan.created - A payment plan was set up
payment_plan.completed - All payments in a plan were completed
payment_plan.canceled - A payment plan was canceled
System Events
company.credits_low - Credit balance is running low
report.generated - A report was generated and is ready for download
The changes field is included when specific field changes are tracked. It shows the previous and new values for each modified field. This field may not be present for all updates.
data.status_change_context is optional. It is included only when the new status is a paid status and that transition was directly caused by a payment transaction.
provider_transaction_id is GetBill’s canonical transaction identifier for the provider.
provider_identifiers includes additional native provider IDs when available.
For Stripe, provider_identifiers may include checkout_session_id, payment_intent_id, and charge_id.
For Axepta, provider_identifiers may include pay_id and trans_id.
For Adyen, provider_identifiers may include payment_link_id and psp_reference.
For SumUp, provider_identifiers may include checkout_id and transaction_id.
For Bridge, provider_identifiers may include payment_link_id, payment_request_id, and client_reference.
For GoCardless, provider_identifiers may include event_id.
For Scellius, PayZen, and SystemPay, provider_identifiers may include order_id and transaction_uuid.
For Monetico, provider_identifiers may include numauto.
Debt Status Values
The status field uses translation key formatstatus.default.* (not simple strings):
Status
Category
Description
When Used
status.default.pending
Initial
Initial state, awaiting processing
Debt just created
status.default.in_progress
Active
Active collection in progress
Collection process started
status.default.processing
Active
Currently being processed
In active workflow
status.default.settled
Resolved
Debt settled through payment plan
Payment plan completed
status.default.paid
Resolved
Fully paid off
Full payment received
status.default.partial
Active
Partially paid
Some payment received
status.default.failed
Closed
Collection failed
Cannot collect (various reasons)
status.default.archived
Closed
Archived/closed
Manually archived
status.default.invalid_phone
Closed
Invalid contact information
Phone number invalid/blocked
status.default.on_hold
Paused
On hold (typically due to dispute)
Collection paused (dispute active)
status.default.no_answer
Active
Voicemail/no answer
Multiple failed contact attempts
status.default.refused
Closed
Debtor refused to pay
Explicit refusal to pay
Important Notes:
Format is always status.default.{name}, never just the name (e.g., "status.default.paid" not "paid")
Companies can create custom statuses with the status.custom.* prefix
When filtering or comparing status values, always use the full format
Media URLs: The audio_url and pdf_url fields contain presigned CloudFront URLs that expire after 1 hour (indicated by media_urls_expire_at). These URLs provide temporary access to call recordings and PDF documents. Important: Cache these URLs but refresh them before the expiration time if you need continued access.
Followup Status Values
The status field uses lowercase underscore format (e.g., deal_found, not DEAL_FOUND):
function handleFollowupCompleted(data) { console.log(`Followup completed: ${data.id}`); // Update local records updateLocalFollowup(data.id, { status: data.status, processed_at: data.processed_at, summary: data.summary, duration_ms: data.duration_ms }); // Download and store call recording if available if (data.audio_url) { downloadAndStoreAudio(data.id, data.audio_url, data.media_urls_expire_at); } // Download and store PDF letter if available if (data.pdf_url) { downloadAndStorePDF(data.id, data.pdf_url, data.media_urls_expire_at); } // Analyze outcome and schedule next action switch (data.status) { case 'deal_found': // Customer agreed to payment plan handlePaymentAgreement(data.debt_id); break; case 'deal_not_found': // Customer refused payment plan scheduleFollowupCall(data.debt_id, 7); // Try again in 7 days break; case 'sent': // Email/SMS sent successfully trackCommunicationSent(data.debt_id, data.type); break; default: console.log(`Followup completed with status: ${data.status}`); } // Update debt priority based on response updateDebtPriority(data.debt_id, data.status);}async function downloadAndStoreAudio(followupId, audioUrl, expiresAt) { try { // Presigned URLs expire after 1 hour - download immediately const response = await fetch(audioUrl); if (!response.ok) { throw new Error(`Failed to download audio: ${response.status}`); } // Store audio file in your system const audioBuffer = await response.arrayBuffer(); await saveAudioToStorage(followupId, audioBuffer); console.log(`Audio recording stored for followup ${followupId}`); } catch (error) { console.error(`Failed to download audio for followup ${followupId}:`, error); // Schedule retry or alert admins }}function handleFollowupFailed(data) { console.log(`Followup failed: ${data.id}`); console.log(`Reason: ${data.failure_reason}`); // Log the failure logFollowupFailure(data.id, data.failure_reason); // Handle based on failure type if (data.status === 'bad_behavior') { // Mark debt for manual review escalateToManualReview(data.debt_id); } else { // Schedule retry with different contact method scheduleAlternativeContact(data.debt_id); }}
GetBill automatically handles webhook delivery retries for you. If your endpoint fails to respond with a 2xx status code or times out, GetBill will automatically retry with exponential backoff:
1st retry: After 1 minute
2nd retry: After 5 minutes
3rd retry: After 30 minutes
After 3 failed attempts, the webhook delivery is marked as permanently failed. You can view the full delivery history and error details in your dashboard under Settings → External Services → Webhook Endpoints → Deliveries.
Important: Make sure your endpoint returns a 2xx status code (like 200 or 204) quickly, even if you queue the actual processing for later. GetBill waits up to 10 seconds for a response before timing out.
For local testing, use ngrok to expose your local server:
# Install ngroknpm install -g ngrok# Start your local servernode server.js# In another terminal, expose port 3000ngrok http 3000# Use the ngrok URL in your webhook configuration# https://abc123.ngrok.io/webhooks/getbill
Always verify webhook signatures to ensure requests come from GetBill
Use HTTPS
Only accept webhooks over HTTPS to protect data in transit
Validate Payloads
Validate webhook payloads before processing to prevent malicious input
Rate Limiting
Implement rate limiting on your webhook endpoints to prevent abuse
Webhooks are a powerful way to keep your application in sync with GetBill in real-time. By following these guidelines and implementing proper error handling, you’ll have a robust integration that scales with your business needs.