Webhooks

Webhooks are the implemented integration surface for API-related events in Repejo today. When a matching event is published, a delivery is created and sent to each active webhook endpoint.

Current state: Webhook endpoints are configured in the Repejo interface and receive signed JSON payloads. A response is considered successful when the endpoint returns HTTP 2xx.

Available events

type EventType =
| "payer.created"
| "payer.updated"
| "subscription.created"
| "subscription.updated"
| "transaction.created"
| "transaction.updated"
| "receivable.created"
| "receivable.updated"
| "payment_session.created"
| "payment_session.succeeded"
| "payment_session.cancelled";

The payment_session.* family is emitted for Payment Sessions ("Pay by Link"); the other families cover the underlying donor/mandate/charge lifecycle.

Both families fire for a completed recurring mandate

A single completed Payment Session emits events from both families, in this order:

payment_session.created     // session created (donor not yet finished)
payment_session.succeeded   // recurring mandate signed
subscription.created        // the resulting subscription/mandate (data.id = sub_…)

Treat payment_session.succeeded as the authoritative reconciliation event for a Payment Session — it carries the metadata you sent on POST /payment_session. Subscribe to whichever family matches your use case (or both), and deduplicate on X-Repejo-Event-ID.

Payload

type WebhookPayload = {
sent_at: string;
event_type: EventType;
data: Payer | Subscription | Receivable | Transaction | PaymentSession;
};

Type note: amount is delivered as a string (e.g. "150"), not a number, in both payment_session.* and subscription.* payloads. Parse it accordingly.

Security: payloads can carry PII

subscription.created.data.method for an Autogiro mandate contains personal data: personal_identity_number (Swedish personnummer), clearing_number, account_number and payer_number.

Treat webhook payloads as sensitive: always verify the signature before parsing, store and transmit the payload securely, and restrict access to the personal data it carries.

Embedded relations

  • Subscription events embed the related payer when the association has been preloaded.
  • Subscription events embed the related payment_method as a nested object with type-specific fields (see PaymentMethod). The field is null when no payment method is linked.
  • Transaction and receivable events always embed the linked payer as a nested object (same fields as the subscription payer). The field is null when no payer is linked or the payer can no longer be loaded (for example, it was deleted).
  • Receivable events always include external_transaction_id. It holds the external id when the receivable has a successful transaction with one, and is null otherwise.
  • Payer events are sent without extra relations unless something else has been added to the entity before publication.

Headers

Content-Type: application/json
User-Agent: Repejo-Webhooks/1.0
X-Repejo-Delivery-ID: <delivery_id>
X-Repejo-Event-ID: <event_id>
X-Repejo-Event-Type: <event_type>
X-Repejo-Attempt: <attempt>
Repejo-Signature: sha256=<hex>

Signature

Repejo-Signature is generated with HMAC-SHA256 over the raw JSON payload and the secret key created for the endpoint.

The signature is sent in the format sha256=<hex>. Always compare against the raw body, not re-serialized JSON.

Retries

Failed deliveries are retried with exponential backoff via Oban. The current configuration performs up to 20 attempts, which spans up to roughly twelve days in total.

If all attempts fail, the delivery is marked as abandoned.

Tutorial: Autogiro mandates managed outside Repejo

This flow is intended for organisations that use Repejo to collect and sign the Autogiro mandate, but keep the Bankgirocentralen connection and recurring charge management in their own CRM or backend.

Typical examples are face-to-face fundraising and website checkouts where Repejo handles the donor experience, while your system owns the downstream Autogiro setup.

1. Create an endpoint

Create an HTTPS endpoint in your system that accepts POST requests with a raw JSON body. Verify the Repejo-Signature header against the raw body before parsing the payload.

Return a 2xx response only after the event has been accepted for processing. Non-2xx responses are retried automatically.

2. Create a webhook in Repejo

In Repejo, create a webhook endpoint that points to your URL and subscribe it to both subscription.created and subscription.updated.

You need both events because a mandate can be created first and then updated as the subscription changes.

3. Receive and filter the events

Your endpoint may receive subscription events for payment methods other than autogiro_external. Always inspect payload.data.payment_method_type and ignore events that are not relevant to your integration.

The same subscription can be delivered multiple times. This can happen because the subscription is updated more than once, and because failed webhook deliveries are retried. Treat the handler as idempotent.

  • Use X-Repejo-Event-ID to deduplicate retries of the same event.
  • Use data.id as the stable subscription identifier in your CRM.
  • Prefer upsert-style processing so repeated subscription.updated deliveries do not create duplicate mandates or payment instructions.

4. Add custom attributes

If you need to link the subscription to an external campaign or source in your CRM, define custom attributes in Repejo and send them through the checkout.

A common pattern is to include a campaign identifier that maps to a Salesforce campaign or another CRM entity.

These values are included in the metadata property on the subscription payload, so your webhook consumer can use payload.data.metadata when creating or updating the record in your CRM.

5. Test the flow

In test, all payment methods are faked, which makes end-to-end testing of this integration straightforward.

Start by setting up a faked payment method in your test environment, then create a checkout that uses it and complete the subscription flow.

Confirm that your endpoint receives subscription.created and, when applicable, subscription.updated with the expected payment_method_type and metadata values.