# Openship GitHub Webhooks Push-to-Deploy: Schema, Verification, and Pipeline Flow

> Automate push-to-deploy with Openship GitHub webhooks. Securely store credentials, verify deliveries with HMAC, and trigger pipelines on push events. Learn the schema and flow.

- Repository: [oblien/openship](https://github.com/oblien/openship)
- Tags: how-to-guide
- Published: 2026-08-19

---

**Openship automates push-to-deploy by storing per-project GitHub webhook credentials in its database schema, verifying inbound deliveries via HMAC signatures, and triggering a deployment pipeline whenever a push event is received.**

The `oblien/openship` platform implements push-to-deploy through native GitHub webhook integration, as highlighted in the project README. By persisting webhook IDs, secrets, and domains directly in the project schema, Openship can securely receive repository events and initiate builds without manual intervention. This article breaks down the database schema, verification logic, and deployment flow based on the actual source code.

## Per-Project Webhook Configuration in the Database

Openship stores every project's GitHub webhook metadata in the project table. According to the source code in [`packages/db/src/schema/project.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/project.ts), the schema defines three critical columns for webhook operations:

```ts
// packages/db/src/schema/project.ts (lines 319-326)
webhookId: integer("webhook_id"),
webhookDomain: text("webhook_domain"),
webhookSecret: text("webhook_secret"),

```

These columns map directly to the GitHub API webhook object. The `webhookId` records the registered hook ID returned by GitHub, `webhookDomain` hosts the public receiver endpoint, and `webhookSecret` stores the shared HMAC key used to sign payloads. You can view the exact schema definition in the repository at [packages/db/src/schema/project.ts](https://github.com/oblien/openship/blob/main/packages/db/src/schema/project.ts#L319-L326).

### Registering a Webhook via the GitHub API

When a project is linked to a repository, Openship generates a unique secret and POSTs a new webhook to the GitHub API. The registration payload points to the project's public domain under `/api/webhooks/github`:

```ts
import fetch from 'node-fetch';

const webhookUrl = `https://${webhookDomain}/api/webhooks/github`;

await fetch(`https://api.github.com/repos/${owner}/${repo}/hooks`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${GITHUB_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'web',
    active: true,
    events: ['push'],
    config: {
      url: webhookUrl,
      secret: webhookSecret,
      content_type: 'json',
    },
  }),
});

```

The `webhookSecret` generated here is persisted alongside the returned `webhookId` so subsequent deliveries can be authenticated.

## Inbound Delivery Schema and Idempotency

All incoming GitHub webhook deliveries are recorded in the `webhook_delivery` table. As implemented in [`packages/db/src/schema/webhook-delivery.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/webhook-delivery.ts), this schema prevents duplicate processing by tracking the GitHub hook and delivery identifiers:

```ts
// packages/db/src/schema/webhook-delivery.ts (lines 20-21)
hookId: integer("hook_id"),
webhookId: integer("webhook_id"),
uniqueIndex("uq_webhook_delivery_github_delivery")

```

The composite unique index on `hookId` and the GitHub delivery ID ensures that retries or redelivered payloads do not spawn multiple deployments. You can inspect the schema at [packages/db/src/schema/webhook-delivery.ts](https://github.com/oblien/openship/blob/main/packages/db/src/schema/webhook-delivery.ts#L20-L21).

## Webhook Verification and Deployment Trigger

The HTTP handler mounted at `POST /api/webhooks/github` performs two tasks before triggering a deployment: HMAC signature verification and delivery persistence. The handler extracts the `X-GitHub-Delivery` header and the `X-Hub-Signature-256` signature to validate the payload against the project's stored secret.

### Signature Verification Logic

```ts
import { verifyGitHubSignature } from './security';
import { recordDelivery } from './db';

export async function githubWebhookHandler(req, res) {
  const signature = req.headers['x-hub-signature-256'];
  const payload = await req.text();

  if (!verifyGitHubSignature(signature, payload, project.webhookSecret)) {
    return res.status(401).send('Invalid signature');
  }

  const deliveryId = req.headers['x-github-delivery'];
  await recordDelivery({
    projectId: project.id,
    hookId: project.webhookId,
    githubDeliveryId: deliveryId,
    payload: JSON.parse(payload),
  });

  await triggerDeploy(project.id, { source: 'webhook' });
  res.status(200).send('OK');
}

```

After verification succeeds, `recordDelivery` inserts the event into the `webhook_delivery` table. If the unique constraint is violated because the delivery was already processed, the pipeline aborts early. Otherwise, `triggerDeploy` enqueues the project's build-detect-run pipeline and marks the deployment source as `webhook`.

## Push-to-Deploy Pipeline Flow

Once the webhook handler validates and records the event, Openship's deployment engine takes over. The pipeline re-detects the repository structure, rebuilds the application artifact, and redeploys the updated container or binary. The edge router then rewrites the virtual host to point to the new build and provisions a fresh TLS certificate if necessary. Because the deployment is triggered directly by the recorded webhook event, the dashboard reflects the new version immediately after the pipeline completes.

## Summary

- **Project-level webhook storage:** [`packages/db/src/schema/project.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/project.ts) stores `webhookId`, `webhookDomain`, and `webhookSecret` for each connected repository.
- **Delivery idempotency:** [`packages/db/src/schema/webhook-delivery.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/webhook-delivery.ts) uses a unique composite index on `hookId` and the GitHub delivery ID to deduplicate inbound events.
- **HMAC verification:** The inbound handler at `POST /api/webhooks/github` verifies `X-Hub-Signature-256` against the stored secret before recording the delivery.
- **Automatic deployment:** Valid push events trigger the build pipeline, redeploy the project, and update the edge routing configuration without manual steps.

## Frequently Asked Questions

### How does Openship verify that a GitHub webhook payload is authentic?

Openship retrieves the per-project `webhookSecret` from [`packages/db/src/schema/project.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/project.ts) and validates the `X-Hub-Signature-256` header using an HMAC-SHA256 comparison. If the computed signature does not match the header, the handler returns a 401 response and never triggers a deployment.

### What prevents the same GitHub push from deploying multiple times?

The `webhook_delivery` schema in [`packages/db/src/schema/webhook-delivery.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/webhook-delivery.ts) enforces a unique index combining `hookId` and the GitHub delivery ID. When a duplicate request arrives, the database insertion fails, and the pipeline exits before rebuilding the project.

### Where does Openship store the GitHub webhook URL and secret?

The project table defined in [`packages/db/src/schema/project.ts`](https://github.com/oblien/openship/blob/main/packages/db/src/schema/project.ts) persists the webhook ID, domain, and secret in the `webhookId`, `webhookDomain`, and `webhookSecret` columns. These fields are populated during the initial repository linking step.

### Which GitHub events does Openship listen to for push-to-deploy?

The registration payload in the webhook setup targets the `push` event. When GitHub transmits a push payload to the project's `/api/webhooks/github` endpoint, the verified event triggers the standard Openship deployment pipeline.