Creating Webhook Jobs
Webhook jobs represent individual delivery tasks. This guide covers everything you need to know about creating and managing webhook jobs.
What is a Webhook Job?
When you send a webhook event through Hook Mesh, we create a job for each subscribed endpoint. Jobs handle:
- Delivering your payload to customer endpoints
- Automatic retries with exponential backoff
- Signature generation for security
- Tracking delivery status and history
Basic Job Creation
Creating a webhook job requires three things:
- application_id - Which application to send from
- event_type - What type of event occurred
- payload - The data to send
Node.js / JavaScript
const response = await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
application_id: 'app_2Zy3X8qP9rK5mN1vB',
event_type: 'user.created',
payload: {
user_id: 'usr_xyz789',
email: 'alice@example.com',
name: 'Alice Johnson',
created_at: '2026-01-20T15:30:00Z'
}
})
});
const job = await response.json();
console.log('Job created:', job.id); // job_5nM8pQ1rK3vL9xBPython
import requests
import os
response = requests.post(
'https://api.hookmesh.com/v1/webhook-jobs',
headers={
'Authorization': f'Bearer {os.environ.get("HOOKMESH_API_KEY")}',
'Content-Type': 'application/json'
},
json={
'application_id': 'app_2Zy3X8qP9rK5mN1vB',
'event_type': 'user.created',
'payload': {
'user_id': 'usr_xyz789',
'email': 'alice@example.com',
'name': 'Alice Johnson',
'created_at': '2026-01-20T15:30:00Z'
}
}
)
job = response.json()
print(f'Job created: {job["id"]}')Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
type WebhookJob struct {
ApplicationID string `json:"application_id"`
EventType string `json:"event_type"`
Payload map[string]interface{} `json:"payload"`
}
func main() {
job := WebhookJob{
ApplicationID: "app_2Zy3X8qP9rK5mN1vB",
EventType: "user.created",
Payload: map[string]interface{}{
"user_id": "usr_xyz789",
"email": "alice@example.com",
"name": "Alice Johnson",
"created_at": "2026-01-20T15:30:00Z",
},
}
jsonData, _ := json.Marshal(job)
req, _ := http.NewRequest(
"POST",
"https://api.hookmesh.com/v1/webhook-jobs",
bytes.NewBuffer(jsonData),
)
req.Header.Set("Authorization", "Bearer "+os.Getenv("HOOKMESH_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("Job created: %s\n", result["id"])
}PHP / Laravel
<?php
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . config('services.hookmesh.api_key'),
])->post('https://api.hookmesh.com/v1/webhook-jobs', [
'application_id' => 'app_2Zy3X8qP9rK5mN1vB',
'event_type' => 'user.created',
'payload' => [
'user_id' => 'usr_xyz789',
'email' => 'alice@example.com',
'name' => 'Alice Johnson',
'created_at' => '2026-01-20T15:30:00Z',
]
]);
$job = $response->json();
echo "Job created: {$job['id']}\n";Response Format
When you create a job, Hook Mesh returns job metadata:
{
"id": "job_5nM8pQ1rK3vL9xB",
"application_id": "app_2Zy3X8qP9rK5mN1vB",
"event_type": "user.created",
"status": "created",
"created_at": "2026-01-20T15:30:00Z",
"expires_at": "2026-01-22T15:30:00Z",
"attempt_count": 0,
"max_retries": 6
}| Field | Description |
|---|---|
id | Unique job identifier |
status | Current state: created, executing, succeeded, awaiting_retry, discarded |
expires_at | Job discarded if not delivered by this time (48 hours) |
max_retries | Maximum retry attempts (6 by default) |
Adding Custom Headers
You can include custom headers that will be sent with every webhook delivery. This is useful for:
- Passing additional context (e.g., tenant ID, environment)
- Adding correlation IDs for tracing
- Custom authentication tokens
await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
application_id: 'app_abc123',
event_type: 'invoice.paid',
payload: {
invoice_id: 'inv_123',
amount: 4900,
currency: 'usd'
},
headers: {
'X-Invoice-ID': 'inv_123',
'X-Tenant-ID': 'tenant_456',
'X-Environment': 'production'
}
})
});Idempotency with Event IDs
To prevent duplicate webhook sends (e.g., from retries or network issues), use the event_id field. If you send the same event_id within 24 hours, Hook Mesh returns the existing job instead of creating a new one.
// First request
const job1 = await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({
application_id: 'app_abc123',
event_type: 'order.shipped',
event_id: 'ord_456_shipped', // Your unique idempotency key
payload: {
order_id: 'ord_456',
tracking_number: '1Z999AA1234567890'
}
})
});
// Creates new job: job_xyz123
// Second request (within 24 hours)
const job2 = await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({
application_id: 'app_abc123',
event_type: 'order.shipped',
event_id: 'ord_456_shipped', // Same idempotency key
payload: {
order_id: 'ord_456',
tracking_number: '1Z999AA1234567890'
}
})
});
// Returns existing job: job_xyz123 (no duplicate created)event_id for important events. Use a unique identifier from your system (e.g., database ID, transaction ID) combined with the event type.Payload Size Limits
Webhook payloads have a maximum size of 256 KB. For most use cases this is plenty, but if you need to send larger data:
❌ Don't send large data directly:
{
"report_id": "rpt_123",
"report_data": "...50MB of CSV data..." // Too large!
}✅ Send a reference + download URL:
{
"report_id": "rpt_123",
"download_url": "https://yourapp.com/api/reports/rpt_123/download",
"expires_at": "2026-01-21T15:30:00Z",
"size_bytes": 52428800,
"format": "csv"
}Checking Job Status
After creating a job, you can check its delivery status:
// Get job details
const response = await fetch(
`https://api.hookmesh.com/v1/webhook-jobs/${jobId}`,
{
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
}
}
);
const job = await response.json();
console.log('Status:', job.status);
console.log('Attempts:', job.attempt_count);
// Check delivery details
if (job.deliveries && job.deliveries.length > 0) {
job.deliveries.forEach(delivery => {
console.log(`Endpoint: ${delivery.endpoint_id}`);
console.log(`Status: ${delivery.status_code}`);
console.log(`Latency: ${delivery.latency_ms}ms`);
});
}Job Lifecycle
Created
Job is queued for delivery
Executing
Currently being delivered to endpoints
Succeeded
Received 2xx response from all endpoints
Awaiting Retry
Failed, will retry with exponential backoff
Discarded
Max retries exceeded or 48-hour window expired
Error Handling
Always handle errors when creating webhook jobs:
try {
const response = await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
application_id: 'app_abc123',
event_type: 'user.created',
payload: { /* ... */ }
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to create job: ${error.error.message}`);
}
const job = await response.json();
console.log('Job created successfully:', job.id);
} catch (error) {
console.error('Error creating webhook job:', error);
// Log to your monitoring service
// Sentry.captureException(error);
// Decide: retry, queue for later, or alert
}Common Errors
Invalid request format or missing required fields. Check your application_id, event_type, and payload structure.
Invalid or missing API key. Verify your HOOKMESH_API_KEY environment variable.
Application or event type doesn't exist. Verify the application_id and that the event_type has been created.
Too many requests. Wait before retrying. Check X-RateLimit-Reset header for when to retry.
Best Practices
- ✅ Always use event_id for idempotency
- ✅ Keep payloads under 100 KB for best performance
- ✅ Use descriptive event types (user.created, not createUser)
- ✅ Include timestamps in your payload
- ✅ Handle errors gracefully with retry logic
- ✅ Log job IDs for debugging and correlation
- ❌ Don't send sensitive data without encryption
- ❌ Don't send large files - use download URLs