Skip to main content
Polling for state changes is fine if you only have a few users. Once you’re at scale, you want push. We support six event types you can subscribe to. Each one fires the moment the underlying state changes — usually within 500 ms of the trigger event. You confirm receipt with a 2xx HTTP response. We retry with exponential backoff on failure.

The events

Subscribing

POST /partner/v1/webhooks:
Response:
Save the secret — it’s shown exactly once. This is what you’ll use to verify every incoming webhook’s signature.

The payload shape

Every webhook delivery is a POST with this shape:
Headers include the signature:

Verifying the signature

Same shape as Stripe and similar. The signature header has two fields:
  • t — the Unix timestamp when we signed.
  • v1 — the HMAC-SHA256 of {timestamp}.{raw_body} using your webhook secret.
Verification:
1

Extract t and v1 from the header

Split on , and parse the key=value pairs.
2

Check the timestamp

Reject if t is more than 5 minutes off from your server’s time (replay protection).
3

Compute the expected signature

HMAC_SHA256(secret, "{t}." + raw_body) and hex-encode.
4

Compare with v1 in constant time

Use a constant-time comparison (hmac.compare_digest in Python, crypto.timingSafeEqual in Node, hash_equals in PHP, CryptographicOperations.FixedTimeEquals in C#, MessageDigest.isEqual in Java). Do not use string equality.
5

Process the event only if signature matches

Otherwise return 401 and log the event for investigation.

Reference implementations

Copy-paste-ready verifiers in five languages (JavaScript, Python, PHP, C#, Java) live on Security → Webhook verification → Verifying in your language. Each one handles signature parsing, timestamp drift checking, HMAC computation, and constant-time comparison. Use those rather than implementing from scratch. Reference implementations in Python, Node.js, and Go on the Partner Portal.

Idempotency

Webhooks can be redelivered. Treat them as at-least-once. Every event has an id (e.g., evt_01HMFB...). Store the ID after successful processing. On the next delivery, check the store — if you’ve seen the ID, return 200 immediately without reprocessing. A simple Redis or Postgres events_processed(event_id PRIMARY KEY, received_at TIMESTAMP) table works for almost everyone. TTL the rows at 30 days.

Looking up an event

You can look up any event your organisation has been delivered, by ID.
This endpoint is open to any authenticated key — no scope required. You can only see events delivered to your own webhooks (the lookup is automatically scoped to your partnerId).

Response 200

string
Event ULID prefixed evt_.
string
Event type, e.g. DISBURSEMENT_DELIVERED.
object
The exact JSON body we POSTed to your endpoint(s).
string
ISO-8601 timestamp the event was raised.
array
One entry per webhook subscription that received this event. Each delivery has the same shape as GET /webhooks/:id/deliveries: id, webhookId, status (PENDING|DELIVERED|FAILED|GIVEN_UP), attempt, lastStatusCode, firstAttemptAt, nextAttemptAt, deliveredAt, givenUpAt, createdAt, lastResponseBody.

Error cases

Use this to reconcile audit-log discrepancies, debug a missed delivery, or correlate a signature you logged with the event we generated.

Delivery and retries

We deliver from a queue. Order is not guaranteed — events for the same user may arrive out of order. Use the createdAt field on the event payload if order matters for your processing. On a non-2xx response (or no response within 30 seconds), we retry with exponential backoff: After 14 days of failed delivery, we mark the event as undeliverable and stop retrying. You can replay it from the Partner Portal dashboard later. We don’t count 200, 201, 202, 204 as success — any 2xx works. We treat 410 Gone as “permanently disabled — don’t retry.”

Filtering

You can configure multiple webhook subscriptions, each filtered to a subset of events. Common patterns:
  • One subscription for consent events (consent.*) routed to your customer-success system.
  • One subscription for standing changes routed to your underwriting service.
  • One subscription for repayment events routed to your loan-servicing system.
Different subscriptions can have different secrets, allowing the receiving teams to operate independently.

Listing and managing subscriptions

Response is an array of subscriptions. Each has stats (delivered count, failed count, average latency over the last 24h). To update an existing subscription (e.g., add or remove events):
To delete:
Deletion is immediate.

Local development

Three good ways to receive webhooks without standing up a backend:
  • webhook.site — instant temporary URL that captures and displays every request it receives, including our headers and the signed body. Best for a quick smoke test.
  • smee.io — forwards public webhook traffic to a local URL on your machine.
  • ngrok — public tunnel to your localhost service.

Testing from the Partner Portal

Inside the Partner Portal at partners.guild.com.ng/webhooks, every active subscription has a Send test action. It fires a synthetic delivery through the same queue and signing pipeline as real events — the only difference is a test: true flag in the envelope so your handler can filter it out if it wants to. The test delivery picks an event your subscription is registered for, generates a representative sample payload, signs it with your webhook secret, and posts it to your URL. It then appears in the deliveries panel with timestamps, response code, and retry status, exactly like a real event. You can also replay any prior delivery from the Portal — useful for debugging a handler crash without waiting for the next live trigger.

Webhook security checklist

The standard list. We won’t enforce all of these but you’ll thank yourself for them later.
  • ✅ Verify signatures on every request. Reject if the signature fails.
  • ✅ Reject events older than 5 minutes (replay protection).
  • ✅ Check idempotency using event_id.
  • ✅ Return 2xx quickly. Don’t do heavy processing inline — push to a queue.
  • ✅ Run handlers in a separate worker if the work is heavy.
  • ✅ Log failed deliveries with the request body for debugging.
  • ✅ Keep the webhook URL stable. If you must change it, update the subscription before changing your DNS.
  • ✅ Monitor delivery success rate. We surface it in the dashboard; alert if it drops below 99%.

Failure modes you’ll see

Next

Rate limits

Throughput numbers for the API.

Snapshots

The data that lives behind every consent.