Webhook Jobs

Webhook jobs represent individual webhook deliveries. Create jobs to send webhooks to your customers' endpoints.

The Webhook Job Object

{
  "id": "job_5nM8pQ1rK3vL9xB",
  "application_id": "app_2Zy3X8qP9rK5mN1vB",
  "event_type": "user.created",
  "event_id": "evt_unique_123",
  "status": "succeeded",
  "attempt_count": 1,
  "max_retries": 6,
  "payload": {
    "user_id": "usr_xyz789",
    "email": "alice@example.com",
    "created_at": "2026-01-20T15:30:00Z"
  },
  "headers": {
    "X-Custom-Header": "value"
  },
  "created_at": "2026-01-20T15:30:00Z",
  "expires_at": "2026-01-22T15:30:00Z",
  "completed_at": "2026-01-20T15:30:05Z"
}
AttributeTypeDescription
idstringUnique identifier for the job
application_idstringApplication this job belongs to
event_typestringEvent type name (e.g., "user.created")
event_idstringOptional idempotency key
statusenumcreated, executing, succeeded, awaiting_retry, discarded, archived
payloadobjectWebhook payload (max 256 KB)
headersobjectCustom headers to send
expires_attimestamp48-hour delivery window

Create Webhook Job

Send a webhook event to all endpoints subscribed to the specified event type.

POST /v1/webhook-jobs

Request Body

{
  "application_id": "app_2Zy3X8qP9rK5mN1vB",
  "event_type": "user.created",
  "event_id": "evt_unique_123",
  "payload": {
    "user_id": "usr_xyz789",
    "email": "alice@example.com",
    "name": "Alice Johnson",
    "created_at": "2026-01-20T15:30:00Z"
  },
  "headers": {
    "X-User-ID": "usr_xyz789",
    "X-Environment": "production"
  }
}
ParameterRequiredDescription
application_idYesApplication ID
event_typeYesEvent type name
payloadYesWebhook payload object
event_idNoIdempotency key (prevents duplicate sends)
headersNoCustom headers object

Response

{
  "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
}

Code Examples

// 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'
    }
  })
});

const job = await response.json();
console.log('Job created:', job.id);
# Python
import requests
import os

response = requests.post(
    'https://api.hookmesh.com/v1/webhook-jobs',
    headers={'Authorization': f'Bearer {os.environ.get("HOOKMESH_API_KEY")}'},
    json={
        'application_id': 'app_2Zy3X8qP9rK5mN1vB',
        'event_type': 'user.created',
        'payload': {
            'user_id': 'usr_xyz789',
            'email': 'alice@example.com'
        }
    }
)

job = response.json()
print(f'Job created: {job["id"]}')
// Go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

func main() {
    payload := map[string]interface{}{
        "application_id": "app_2Zy3X8qP9rK5mN1vB",
        "event_type":     "user.created",
        "payload": map[string]string{
            "user_id": "usr_xyz789",
            "email":   "alice@example.com",
        },
    }

    body, _ := json.Marshal(payload)
    req, _ := http.NewRequest("POST",
        "https://api.hookmesh.com/v1/webhook-jobs",
        bytes.NewBuffer(body))

    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 job map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&job)
    fmt.Printf("Job created: %s\n", job["id"])
}
<?php
// PHP
$ch = curl_init('https://api.hookmesh.com/v1/webhook-jobs');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('HOOKMESH_API_KEY'),
        'Content-Type: application/json'
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'application_id' => 'app_2Zy3X8qP9rK5mN1vB',
        'event_type' => 'user.created',
        'payload' => [
            'user_id' => 'usr_xyz789',
            'email' => 'alice@example.com'
        ]
    ])
]);

$response = curl_exec($ch);
$job = json_decode($response, true);
echo "Job created: {$job['id']}\n";

curl_close($ch);

Retrieve Webhook Job

GET /v1/webhook-jobs/{job_id}

Response

{
  "id": "job_5nM8pQ1rK3vL9xB",
  "application_id": "app_2Zy3X8qP9rK5mN1vB",
  "event_type": "user.created",
  "status": "succeeded",
  "attempt_count": 1,
  "payload": {...},
  "deliveries": [
    {
      "endpoint_id": "ep_xyz789",
      "status_code": 200,
      "response_body": "{\"received\":true}",
      "latency_ms": 145,
      "attempted_at": "2026-01-20T15:30:05Z"
    }
  ]
}

List Webhook Jobs

GET /v1/webhook-jobs?application_id={id}&status={status}

Query Parameters

ParameterRequiredDescription
application_idYesFilter by application
statusNoFilter by status
event_typeNoFilter by event type
endpoint_idNoFilter by endpoint
created_afterNoISO 8601 timestamp
created_beforeNoISO 8601 timestamp
pageNoPage number (default: 1)
per_pageNoItems per page (default: 25, max: 100)

Response

{
  "data": [
    {
      "id": "job_5nM8pQ1rK3vL9xB",
      "event_type": "user.created",
      "status": "succeeded",
      "created_at": "2026-01-20T15:30:00Z"
    }
  ],
  "pagination": {
    "total": 1543,
    "page": 1,
    "per_page": 25,
    "total_pages": 62
  }
}

Filtering Jobs

The List endpoint supports multiple query parameters for filtering webhook jobs. Here are common filtering patterns:

Filter by Status

Retrieve all failed jobs to investigate delivery issues:

// Node.js - Get all failed jobs
const response = await fetch(
  'https://api.hookmesh.com/v1/webhook-jobs?' +
  `application_id=${appId}&` +
  'status=failed',
  {
    headers: {
      'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
    }
  }
);

const { data: failedJobs } = await response.json();
console.log(`Found ${failedJobs.length} failed jobs`);
# Python - Get all failed jobs
import requests
import os

response = requests.get(
    'https://api.hookmesh.com/v1/webhook-jobs',
    headers={'Authorization': f'Bearer {os.getenv("HOOKMESH_API_KEY")}'},
    params={
        'application_id': app_id,
        'status': 'failed'
    }
)

failed_jobs = response.json()['data']
print(f'Found {len(failed_jobs)} failed jobs')

Filter by Date Range

Get jobs from the last 24 hours:

// Node.js - Last 24 hours
const yesterday = new Date();
yesterday.setHours(yesterday.getHours() - 24);

const response = await fetch(
  'https://api.hookmesh.com/v1/webhook-jobs?' +
  `application_id=${appId}&` +
  `created_after=${yesterday.toISOString()}`,
  {
    headers: {
      'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
    }
  }
);

const { data: recentJobs } = await response.json();
# Python - Last 24 hours
from datetime import datetime, timedelta

yesterday = datetime.now() - timedelta(hours=24)

response = requests.get(
    'https://api.hookmesh.com/v1/webhook-jobs',
    headers={'Authorization': f'Bearer {os.getenv("HOOKMESH_API_KEY")}'},
    params={
        'application_id': app_id,
        'created_after': yesterday.isoformat()
    }
)

Filter by Event Type

Get all jobs for a specific event:

// Node.js - Filter by event type
const response = await fetch(
  'https://api.hookmesh.com/v1/webhook-jobs?' +
  `application_id=${appId}&` +
  'event_type=user.created',
  {
    headers: {
      'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
    }
  }
);

Combine Multiple Filters

Find failed payment webhooks from the last week:

// Node.js - Combined filters
const lastWeek = new Date();
lastWeek.setDate(lastWeek.getDate() - 7);

const response = await fetch(
  'https://api.hookmesh.com/v1/webhook-jobs?' +
  `application_id=${appId}&` +
  'event_type=payment.failed&' +
  'status=failed&' +
  `created_after=${lastWeek.toISOString()}&` +
  'page=1&' +
  'per_page=50',
  {
    headers: {
      'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
    }
  }
);

const { data: jobs, pagination } = await response.json();
console.log(`Found ${pagination.total} failed payment webhooks in the last week`);
# Python - Combined filters
from datetime import datetime, timedelta

last_week = datetime.now() - timedelta(days=7)

response = requests.get(
    'https://api.hookmesh.com/v1/webhook-jobs',
    headers={'Authorization': f'Bearer {os.getenv("HOOKMESH_API_KEY")}'},
    params={
        'application_id': app_id,
        'event_type': 'payment.failed',
        'status': 'failed',
        'created_after': last_week.isoformat(),
        'page': 1,
        'per_page': 50
    }
)

result = response.json()
print(f'Found {result["pagination"]["total"]} failed payment webhooks')
// Go - Combined filters
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "os"
    "time"
)

func main() {
    lastWeek := time.Now().AddDate(0, 0, -7)

    params := url.Values{}
    params.Add("application_id", appID)
    params.Add("event_type", "payment.failed")
    params.Add("status", "failed")
    params.Add("created_after", lastWeek.Format(time.RFC3339))
    params.Add("page", "1")
    params.Add("per_page", "50")

    req, _ := http.NewRequest("GET",
        "https://api.hookmesh.com/v1/webhook-jobs?"+params.Encode(),
        nil)

    req.Header.Set("Authorization", "Bearer "+os.Getenv("HOOKMESH_API_KEY"))

    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)
    pagination := result["pagination"].(map[string]interface{})
    fmt.Printf("Found %.0f failed payment webhooks\n", pagination["total"])
}
<?php
// PHP - Combined filters
$lastWeek = new DateTime('-7 days');

$params = http_build_query([
    'application_id' => $appId,
    'event_type' => 'payment.failed',
    'status' => 'failed',
    'created_after' => $lastWeek->format(DateTime::ISO8601),
    'page' => 1,
    'per_page' => 50
]);

$ch = curl_init('https://api.hookmesh.com/v1/webhook-jobs?' . $params);

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('HOOKMESH_API_KEY')
    ]
]);

$response = curl_exec($ch);
$result = json_decode($response, true);
echo "Found {$result['pagination']['total']} failed payment webhooks\n";

curl_close($ch);

Pagination with Filters

When filtering returns many results, use pagination:

// Node.js - Paginate through filtered results
async function getAllFailedJobs(appId: string) {
  const allJobs = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await fetch(
      'https://api.hookmesh.com/v1/webhook-jobs?' +
      `application_id=${appId}&` +
      'status=failed&' +
      `page=${page}&` +
      'per_page=100',
      {
        headers: {
          'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`
        }
      }
    );

    const { data, pagination } = await response.json();
    allJobs.push(...data);

    hasMore = page < pagination.total_pages;
    page++;
  }

  return allJobs;
}
Performance Tip: Always include application_id in your filters for best performance. Combine created_after and created_before to limit the date range when searching for specific jobs.

Retry Webhook Job

Manually retry a failed webhook job. Resets the job to created status and re-enters the queue.

POST /v1/webhook-jobs/{job_id}/retry

Response

{
  "id": "job_5nM8pQ1rK3vL9xB",
  "status": "created",
  "attempt_count": 0
}
Useful when: Customer fixed their endpoint and wants immediate retry instead of waiting for the next scheduled retry.

Cancel Webhook Job

Cancel a pending webhook job. Only works for jobs in created or awaiting_retry status.

POST /v1/webhook-jobs/{job_id}/cancel

Response

{
  "id": "job_5nM8pQ1rK3vL9xB",
  "status": "discarded",
  "discarded_at": "2026-01-20T15:30:00Z"
}

Idempotency

Prevent duplicate webhook sends by providing an event_id. If you send the same event_id within 24 hours, Hook Mesh returns the existing job instead of creating a new one.

// First request
await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
  method: 'POST',
  headers: {...},
  body: JSON.stringify({
    application_id: 'app_xyz',
    event_type: 'invoice.paid',
    event_id: 'inv_123_paid', // Idempotency key
    payload: {...}
  })
});
// Creates new job

// Second request (within 24 hours)
await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
  method: 'POST',
  headers: {...},
  body: JSON.stringify({
    application_id: 'app_xyz',
    event_type: 'invoice.paid',
    event_id: 'inv_123_paid', // Same key
    payload: {...}
  })
});
// Returns existing job, doesn't create duplicate
Best practice: Use event_id for all webhook sends to prevent duplicates from retries or network issues.